Skip to content

Commit faaf083

Browse files
committed
build: updating npm publish script, creating zips for github releases
1 parent ddb1fed commit faaf083

3 files changed

Lines changed: 195 additions & 4 deletions

File tree

.github/workflows/npm-publish.yml

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77

88
permissions:
99
id-token: write
10-
contents: read
10+
contents: write
1111

1212
concurrency:
1313
group: ${{ github.workflow }}-${{ github.ref }}
@@ -16,6 +16,7 @@ concurrency:
1616
env:
1717
NX_CLOUD_DISTRIBUTED_EXECUTION: true
1818
NX_CLOUD_ACCESS_TOKEN: '${{ secrets.NX_CLOUD_ACCESS_TOKEN }}'
19+
IS_STABLE: ${{ !contains(github.ref, '-alpha.') && !contains(github.ref, '-beta.') }}
1920

2021
jobs:
2122
publish:
@@ -43,8 +44,7 @@ jobs:
4344

4445
- name: Get pnpm store directory
4546
id: pnpm-cache
46-
run: |
47-
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
47+
run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
4848

4949
- uses: actions/cache@v5
5050
name: Setup pnpm cache
@@ -62,10 +62,15 @@ jobs:
6262
env:
6363
CI: true
6464

65+
# 📦 ZIP artifacts (solo stable)
66+
- name: Generate zip artifacts
67+
if: env.IS_STABLE == 'true'
68+
run: node scripts/package-zips.js
69+
70+
# 🚀 Publish npm
6571
- name: Publish to NPM (OIDC Auth)
6672
run: |
6773
TAG="${GITHUB_REF#refs/tags/}"
68-
6974
BASE_CMD="npx lerna publish from-package --ignore-scripts"
7075
7176
echo "Publishing tag: $TAG via OIDC"
@@ -84,6 +89,30 @@ jobs:
8489
$BASE_CMD
8590
fi
8691
92+
# 🧠 Create release (raw GitHub notes)
93+
- name: Create Release (raw)
94+
if: env.IS_STABLE == 'true'
95+
id: release
96+
uses: softprops/action-gh-release@v2
97+
with:
98+
files: release-artifacts/*.zip
99+
generate_release_notes: true
100+
101+
# ✨ Prettify changelog → per package
102+
- name: Prettify changelog
103+
if: env.IS_STABLE == 'true'
104+
run: |
105+
echo "${{ steps.release.outputs.body }}" > RAW_RELEASE.md
106+
node scripts/prettify-changelog.js
107+
108+
# 🔁 Update release con changelog finale
109+
- name: Update Release Notes
110+
if: env.IS_STABLE == 'true'
111+
uses: softprops/action-gh-release@v2
112+
with:
113+
files: release-artifacts/*.zip
114+
body_path: RELEASE_NOTES.md
115+
87116
- name: Stop Nx Cloud Session
88117
run: npx nx fix-ci
89118
if: always()

scripts/package-zips.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { execSync } from "node:child_process";
2+
import { readdirSync, existsSync, readFileSync, mkdirSync, rmSync } from "node:fs";
3+
import { join } from "node:path";
4+
5+
const ROOT = process.cwd();
6+
const OUTPUT_DIR = join(ROOT, "release-artifacts");
7+
8+
// Clean output directory
9+
rmSync(OUTPUT_DIR, { recursive: true, force: true });
10+
mkdirSync(OUTPUT_DIR, { recursive: true });
11+
12+
/**
13+
* Recursively find all directories containing a package.json
14+
*/
15+
function findPackages(dir) {
16+
const entries = readdirSync(dir, { withFileTypes: true });
17+
const packages = [];
18+
19+
for (const entry of entries) {
20+
const fullPath = join(dir, entry.name);
21+
22+
if (entry.isDirectory()) {
23+
const packageJsonPath = join(fullPath, "package.json");
24+
25+
if (existsSync(packageJsonPath)) {
26+
packages.push(fullPath);
27+
}
28+
29+
// Continue searching nested folders
30+
packages.push(...findPackages(fullPath));
31+
}
32+
}
33+
34+
return packages;
35+
}
36+
37+
/**
38+
* Create a zip from a dist folder
39+
*/
40+
function createZip(distPath, outputZipPath) {
41+
console.log(`📦 Creating ${outputZipPath}`);
42+
43+
execSync(`cd "${distPath}" && zip -r "${outputZipPath}" .`, {
44+
stdio: "inherit",
45+
});
46+
}
47+
48+
// 🔍 Find all packages in repo
49+
const allPackages = findPackages(ROOT);
50+
51+
for (const pkgPath of allPackages) {
52+
const distPath = join(pkgPath, "dist");
53+
const packageJsonPath = join(pkgPath, "package.json");
54+
55+
// Skip if no dist folder
56+
if (!existsSync(distPath)) continue;
57+
58+
const pkgJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
59+
60+
const name = pkgJson.name
61+
.replace(/^@[^/]+\//, "") // remove scope if present
62+
.replace(/\//g, "-"); // safety
63+
64+
const version = pkgJson.version;
65+
66+
const zipName = `${name}-${version}.zip`;
67+
const zipPath = join(OUTPUT_DIR, zipName);
68+
69+
createZip(distPath, zipPath);
70+
}
71+
72+
console.log("✅ All package archives created successfully.");

scripts/prettify-changelog.js.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { execSync } from "node:child_process";
2+
import { readFileSync, writeFileSync } from "node:fs";
3+
4+
// Read raw release notes generated by GitHub
5+
const raw = readFileSync("RAW_RELEASE.md", "utf-8");
6+
const lines = raw.split("\n");
7+
8+
/**
9+
* Map a file path to a logical package name.
10+
* Adjusted for monorepo structure.
11+
*/
12+
function getPackageFromPath(filePath) {
13+
const parts = filePath.split("/");
14+
15+
if (parts.length === 0) return null;
16+
17+
// Ignore demo folder
18+
if (parts[0] === "demo") return null;
19+
20+
// Ignore test utilities
21+
if (parts[0] === "utils" && parts[1] === "tests") return null;
22+
23+
// Single package
24+
if (parts[0] === "engine") return "engine";
25+
26+
// Root utils (excluding tests)
27+
if (parts[0] === "utils") return "utils";
28+
29+
// Generic structure: <group>/<package>
30+
if (parts.length >= 2) {
31+
return `${parts[0]}/${parts[1]}`;
32+
}
33+
34+
return null;
35+
}
36+
37+
/**
38+
* Extract affected packages from a PR using GitHub CLI.
39+
*/
40+
function getPackagesFromPR(prNumber) {
41+
try {
42+
const result = execSync(`gh pr view ${prNumber} --json files`, { encoding: "utf-8" });
43+
44+
const files = JSON.parse(result).files;
45+
const packages = new Set();
46+
47+
for (const file of files) {
48+
const pkg = getPackageFromPath(file.path);
49+
if (pkg) {
50+
packages.add(pkg);
51+
}
52+
}
53+
54+
return [...packages];
55+
} catch (error) {
56+
console.warn(`Failed to fetch PR #${prNumber}:`, error.message);
57+
return [];
58+
}
59+
}
60+
61+
let output = "";
62+
63+
// Process each line of the generated release notes
64+
for (const line of lines) {
65+
/**
66+
* Match lines like:
67+
* - Add new feature (#123)
68+
*/
69+
const match = line.match(/- (.+?) \(#(\d+)\)/);
70+
71+
if (!match) {
72+
output += line + "\n";
73+
continue;
74+
}
75+
76+
const [, text, prNumber] = match;
77+
78+
const packages = getPackagesFromPR(prNumber);
79+
80+
if (packages.length === 0) {
81+
output += `- ${text}\n`;
82+
} else {
83+
output += `- ${text} (${packages.join(", ")})\n`;
84+
}
85+
}
86+
87+
// Write final formatted changelog
88+
writeFileSync("RELEASE_NOTES.md", output);
89+
90+
console.log("✅ Changelog successfully enhanced with affected packages.");

0 commit comments

Comments
 (0)