Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6ef1ad7
refactor: extract logic to grid plugin
iobuhov Nov 7, 2025
7ddc5ea
refactor: rewrite empty placeholder
iobuhov Nov 7, 2025
77be70d
refactor: separate code in plugin
iobuhov Nov 11, 2025
0be25ef
refactor: split select-all feature
iobuhov Nov 13, 2025
4233ac4
style: fix comment
iobuhov Nov 13, 2025
0fea5d4
style: fix comments & type
iobuhov Nov 13, 2025
d761425
fix: update test types
iobuhov Nov 17, 2025
5fa01c4
refactor: rewrite dg pagination
iobuhov Nov 17, 2025
b387a47
refactor: rewrite grid style to atom
iobuhov Nov 17, 2025
8340573
refactor: rewrite grid body
iobuhov Nov 18, 2025
9c080c7
test: add new tests
iobuhov Nov 18, 2025
180dbec
test: add grid component test
iobuhov Nov 18, 2025
2093899
refactor: rewire grid header props
iobuhov Nov 18, 2025
4bc9e46
refactor: deprecate dg basic data
iobuhov Nov 18, 2025
a59dbc6
refactor: rewrite dg rows renderer
iobuhov Nov 18, 2025
2bdc19c
refactor: remove props from grid
iobuhov Nov 20, 2025
3150f3a
refactor: rewrite export widget
iobuhov Nov 20, 2025
a3f666d
refactor: rename Cell to DataCell
iobuhov Nov 20, 2025
6e03bf5
refactor: add new select actions provider
iobuhov Nov 20, 2025
7d5fb4c
WIP
iobuhov Nov 20, 2025
8a898fc
chore: add oss clearance tools
r0b1n Oct 30, 2025
bb615aa
chore: don't repackage mpk, just include readme
r0b1n Nov 6, 2025
607bb4b
refactor: extract drag & drop state and logic to mobx
yordan-st Nov 20, 2025
ea07656
refactor: update components to use new state management
yordan-st Nov 20, 2025
71f7d6b
refactor: remove obsolete tests and snapshots, create for new component
yordan-st Nov 20, 2025
8f73a06
refactor: rewrite columnreszier to use injection hooks
yordan-st Nov 20, 2025
0f9af81
refactor: enhance ColumnResizer test structure and update snapshot
yordan-st Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 307 additions & 0 deletions automation/utils/bin/rui-oss-clearance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
#!/usr/bin/env ts-node-script

import { gh, GitHubDraftRelease, GitHubReleaseAsset } from "../src/github";
import { basename, join } from "path";
import { prompt } from "enquirer";
import chalk from "chalk";
import { createReadStream } from "node:fs";
import * as crypto from "crypto";
import { pipeline } from "stream/promises";
import { homedir } from "node:os";
import {
createSBomGeneratorFolderStructure,
findAllReadmeOssLocally,
generateSBomArtifactsInFolder,
getRecommendedReadmeOss,
includeReadmeOssIntoMpk
} from "../src/oss-clearance";

// ============================================================================
// Constants
// ============================================================================

const SBOM_GENERATOR_JAR = join(homedir(), "SBOM_Generator.jar");

// ============================================================================
// Utility Functions
// ============================================================================

function printHeader(title: string): void {
console.log("\n" + chalk.bold.cyan("═".repeat(60)));
console.log(chalk.bold.cyan(` ${title}`));
console.log(chalk.bold.cyan("═".repeat(60)) + "\n");
}

function printStep(step: number, total: number, message: string): void {
console.log(chalk.bold.blue(`\n[${step}/${total}]`) + chalk.white(` ${message}`));
}

function printSuccess(message: string): void {
console.log(chalk.green(`✅ ${message}`));
}

function printError(message: string): void {
console.log(chalk.red(`❌ ${message}`));
}

function printWarning(message: string): void {
console.log(chalk.yellow(`⚠️ ${message}`));
}

function printInfo(message: string): void {
console.log(chalk.cyan(`ℹ️ ${message}`));
}

function printProgress(message: string): void {
console.log(chalk.gray(` → ${message}`));
}

// ============================================================================
// Core Functions
// ============================================================================

async function verifyGitHubAuth(): Promise<void> {
printStep(1, 5, "Verifying GitHub authentication...");

try {
await gh.ensureAuth();
printSuccess("GitHub authentication verified");
} catch (error) {
printError(`GitHub authentication failed: ${(error as Error).message}`);
console.log(chalk.yellow("\n💡 Setup Instructions:\n"));
console.log(chalk.white("1. Install GitHub CLI:"));
console.log(chalk.cyan(" • Download: https://cli.github.com/"));
console.log(chalk.cyan(" • Or via brew: brew install gh\n"));
console.log(chalk.white("2. Authenticate (choose one option):"));
console.log(chalk.cyan(" • Option A: export GITHUB_TOKEN=your_token_here"));
console.log(chalk.cyan(" • Option B: export GH_PAT=your_token_here"));
console.log(chalk.cyan(" • Option C: gh auth login\n"));
console.log(chalk.white("3. For A and B get your token at:"));
console.log(chalk.cyan(" https://github.com/settings/tokens\n"));
throw new Error("GitHub authentication required");
}
}

async function selectRelease(): Promise<GitHubDraftRelease> {
printStep(2, 5, "Fetching draft releases...");

const releases = await gh.getDraftReleases();
printSuccess(`Found ${releases.length} draft release${releases.length !== 1 ? "s" : ""}`);

if (releases.length === 0) {
printWarning(
"No draft releases found. Please create a draft release before trying again using `prepare-release` tool"
);
throw new Error("No draft releases found");
}

console.log(); // spacing
const { tag_name } = await prompt<{ tag_name: string }>({
type: "select",
name: "tag_name",
message: "Select a release to process:",
choices: releases.map(r => ({
name: r.tag_name,
message: `${r.name} ${chalk.gray(`(${r.tag_name})`)}`
}))
});

const release = releases.find(r => r.tag_name === tag_name);
if (!release) {
throw new Error(`Release not found: ${tag_name}`);
}

printInfo(`Selected release: ${chalk.bold(release.name)}`);
return release;
}

async function findAndValidateMpkAsset(release: GitHubDraftRelease): Promise<GitHubReleaseAsset> {
printStep(3, 5, "Locating MPK asset...");

const mpkAsset = release.assets.find(asset => asset.name.endsWith(".mpk"));

if (!mpkAsset) {
printError("No MPK asset found in release");
printInfo(`Available assets: ${release.assets.map(a => a.name).join(", ")}`);
throw new Error("MPK asset not found");
}

printSuccess(`Found MPK asset: ${chalk.bold(mpkAsset.name)}`);
printInfo(`Asset ID: ${mpkAsset.id}`);
return mpkAsset;
}

async function downloadAndVerifyAsset(mpkAsset: GitHubReleaseAsset, downloadPath: string): Promise<string> {
printStep(4, 5, "Downloading and verifying MPK asset...");

printProgress(`Downloading to: ${downloadPath}`);
await gh.downloadReleaseAsset(mpkAsset.id, downloadPath);
printSuccess("Download completed");

printProgress("Computing SHA-256 hash...");
const fileHash = await computeHash(downloadPath);
printInfo(`Computed hash: ${fileHash}`);

const expectedDigest = mpkAsset.digest.replace("sha256:", "");
if (fileHash !== expectedDigest) {
printError("Hash mismatch detected!");
printInfo(`Expected: ${expectedDigest}`);
printInfo(`Got: ${fileHash}`);
throw new Error("Asset integrity verification failed");
}

printSuccess("Hash verification passed");
return fileHash;
}

async function runSbomGenerator(tmpFolder: string, releaseName: string, fileHash: string): Promise<string> {
printStep(5, 5, "Running SBOM Generator...");

printProgress("Generating OSS Clearance artifacts...");

const finalName = `${releaseName} [${fileHash}].zip`;
const finalPath = join(homedir(), "Downloads", finalName);

await generateSBomArtifactsInFolder(tmpFolder, SBOM_GENERATOR_JAR, releaseName, finalPath);
printSuccess("Completed.");

return finalPath;
}

async function computeHash(filepath: string): Promise<string> {
const input = createReadStream(filepath);
const hash = crypto.createHash("sha256");
await pipeline(input, hash);
return hash.digest("hex");
}

// ============================================================================
// Command Handlers
// ============================================================================

async function handlePrepareCommand(): Promise<void> {
printHeader("OSS Clearance Artifacts Preparation");

try {
// Step 1: Verify authentication
await verifyGitHubAuth();

// Step 2: Select release
const release = await selectRelease();

// Step 3: Find MPK asset
const mpkAsset = await findAndValidateMpkAsset(release);

// Prepare folder structure
const [tmpFolder, downloadPath] = await createSBomGeneratorFolderStructure(release.name);
printInfo(`Working directory: ${tmpFolder}`);

// Step 4: Download and verify
const fileHash = await downloadAndVerifyAsset(mpkAsset, downloadPath);

// Step 5: Run SBOM Generator
const finalPath = await runSbomGenerator(tmpFolder, release.name, fileHash);

console.log(chalk.bold.green(`\n🎉 Success! Output file:`));
console.log(chalk.cyan(` ${finalPath}\n`));
} catch (error) {
console.log("\n" + chalk.bold.red("═".repeat(60)));
printError(`Process failed: ${(error as Error).message}`);
console.log(chalk.bold.red("═".repeat(60)) + "\n");
process.exit(1);
}
}

async function handleIncludeCommand(): Promise<void> {
printHeader("OSS Clearance Readme Include");

try {
// TODO: Implement include command logic
// Step 1: Verify authentication
await verifyGitHubAuth();

// Step 2: Select release
const release = await selectRelease();

// Step 3: Find MPK asset
const mpkAsset = await findAndValidateMpkAsset(release);

// Step 4: Find and select OSS Readme
const readmes = findAllReadmeOssLocally();
const recommendedReadmeOss = getRecommendedReadmeOss(
release.name.split(" ")[0],
release.name.split(" ")[1],
readmes
);

let readmeToInclude: string;

if (!recommendedReadmeOss) {
const { selectedReadme } = await prompt<{ selectedReadme: string }>({
type: "select",
name: "selectedReadme",
message: "Select a release to process:",
choices: readmes.map(r => ({
name: r,
message: basename(r)
}))
});

readmeToInclude = selectedReadme;
} else {
readmeToInclude = recommendedReadmeOss;
}

printInfo(`Readme to include: ${readmeToInclude}`);

// Step 7: Upload updated asses to the draft release
const newAsset = await gh.uploadReleaseAsset(release.id, readmeToInclude, basename(readmeToInclude));
console.log(`Successfully uploaded asset ${newAsset.name} (ID: ${newAsset.id})`);

console.log(release.id);
} catch (error) {
console.log("\n" + chalk.bold.red("═".repeat(60)));
printError(`Process failed: ${(error as Error).message}`);
console.log(chalk.bold.red("═".repeat(60)) + "\n");
process.exit(1);
}
}

// ============================================================================
// Main Function
// ============================================================================

async function main(): Promise<void> {
const command = process.argv[2];

switch (command) {
case "prepare":
await handlePrepareCommand();
break;
case "include":
await handleIncludeCommand();
break;
default:
printError(command ? `Unknown command: ${command}` : "No command specified");
console.log(chalk.white("\nUsage:"));
console.log(
chalk.cyan(" rui-oss-clearance.ts prepare ") +
chalk.gray("- Prepare OSS clearance artifact from draft release")
);
console.log(
chalk.cyan(" rui-oss-clearance.ts include ") +
chalk.gray("- Include OSS Readme file into a draft release")
);
console.log();
process.exit(1);
}
}

// ============================================================================
// Entry Point
// ============================================================================

main().catch(e => {
console.error(chalk.red("\n💥 Unexpected error:"), e);
process.exit(1);
});
6 changes: 2 additions & 4 deletions automation/utils/bin/rui-prepare-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,15 +353,13 @@ async function createReleaseBranch(packageName: string, version: string): Promis
}

async function initializeJiraClient(): Promise<Jira> {
const projectKey = process.env.JIRA_PROJECT_KEY;
const baseUrl = process.env.JIRA_BASE_URL;
const projectKey = process.env.JIRA_PROJECT_KEY ?? "WC";
const baseUrl = process.env.JIRA_BASE_URL ?? "https://mendix.atlassian.net";
const apiToken = process.env.JIRA_API_TOKEN;

if (!projectKey || !baseUrl || !apiToken) {
console.error(chalk.red("❌ Missing Jira environment variables"));
console.log(chalk.dim(" Required variables:"));
console.log(chalk.dim(" export JIRA_PROJECT_KEY=WEB"));
console.log(chalk.dim(" export JIRA_BASE_URL=https://your-company.atlassian.net"));
console.log(chalk.dim(" export [email protected]:ATATT3xFfGF0..."));
console.log(chalk.dim(" Get your API token at: https://id.atlassian.com/manage-profile/security/api-tokens"));
throw new Error("Missing Jira environment variables");
Expand Down
1 change: 1 addition & 0 deletions automation/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"compile:parser:widget": "peggy -o ./src/changelog-parser/parser/module/module.js ./src/changelog-parser/parser/module/module.pegjs",
"format": "prettier --write .",
"lint": "eslint --ext .jsx,.js,.ts,.tsx src/",
"oss-clearance": "ts-node bin/rui-oss-clearance.ts",
"prepare": "pnpm run compile:parser:widget && pnpm run compile:parser:module && tsc",
"prepare-release": "ts-node bin/rui-prepare-release.ts",
"start": "tsc --watch",
Expand Down
9 changes: 0 additions & 9 deletions automation/utils/src/changelog.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { gh } from "./github";
import { PublishedInfo } from "./package-info";
import { exec, popd, pushd } from "./shell";
import { findOssReadme } from "./oss-readme";
import { join } from "path";

export async function updateChangelogsAndCreatePR(
info: PublishedInfo,
Expand Down Expand Up @@ -53,13 +51,6 @@ export async function updateChangelogsAndCreatePR(
pushd(root.trim());
await exec(`git add '*/CHANGELOG.md'`);

const path = process.cwd();
const readmeossFile = findOssReadme(path, info.mxpackage.name, info.version.format());
if (readmeossFile) {
console.log(`Removing OSS clearance readme file '${readmeossFile}'...`);
await exec(`git rm '${readmeossFile}'`);
}

await exec(`git commit -m "chore(${info.name}): update changelog"`);
await exec(`git push ${remoteName} ${releaseBranchName}`);
popd();
Expand Down
Loading
Loading