Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets).

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
13 changes: 13 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.3/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"compose-documentation"
]
}
6 changes: 6 additions & 0 deletions .changeset/lovely-cities-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@perfect-abstractions/compose-cli": patch
"@perfect-abstractions/compose": patch
---

first publised release: core facet library (@perfect-abstraction/compose) and CLI (@perfect-abstraction/compose-cli)
20 changes: 20 additions & 0 deletions .github/publish-packages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"packages": [
{
"id": "library",
"workspace": "@perfect-abstractions/compose",
"versionFile": "src/package.json",
"tagPrefix": "compose",
"needsFoundry": true,
"check": "npm run compose@check && npm run compose@pack:check"
},
{
"id": "cli",
"workspace": "@perfect-abstractions/compose-cli",
"versionFile": "cli/package.json",
"tagPrefix": "compose-cli",
"needsFoundry": false,
"check": "npm run cli@check && npm run cli@pack:check"
}
]
}
2 changes: 2 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Before submitting this PR, please ensure:

- [ ] **Documentation updated** - If applicable, update relevant documentation

- [ ] **Changesets** — If this PR changes publishable packages (`src/`, `cli/`), [changeset-bot](https://github.com/apps/changeset-bot) will comment if a release note might be needed. You can add one when convenient or defer to maintainers.

Make sure to follow the [contributing](https://compose.diamonds/docs/contribution/how-to-contribute) guidelines.

## Additional Notes
Expand Down
110 changes: 110 additions & 0 deletions .github/scripts/publish-plan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Reads .github/publish-packages.json, compares each package version to npm,
* and writes matrix JSON + has_publish to GITHUB_OUTPUT for publish.yml.
*/

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const CONFIG_PATH = path.join(process.cwd(), '.github/publish-packages.json');

function npmViewVersion(packageName) {
try {
return execSync(`npm view ${JSON.stringify(packageName)} version`, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
} catch {
return '';
}
}

function readLocalVersion(versionFile) {
const abs = path.join(process.cwd(), versionFile);
const raw = fs.readFileSync(abs, 'utf8');
const pkg = JSON.parse(raw);
if (typeof pkg.version !== 'string' || !pkg.version) {
throw new Error(`Invalid or missing version in ${versionFile}`);
}
return pkg.version;
}

function loadConfig() {
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
const config = JSON.parse(raw);
if (!config.packages || !Array.isArray(config.packages)) {
throw new Error('publish-packages.json must contain a "packages" array');
}
return config.packages;
}

function validateEntries(packages) {
const ids = new Set();
const workspaces = new Set();
const required = ['id', 'workspace', 'versionFile', 'tagPrefix', 'check', 'needsFoundry'];

for (const p of packages) {
for (const key of required) {
if (!(key in p)) {
throw new Error(`Package entry missing required field "${key}": ${JSON.stringify(p)}`);
}
}
if (typeof p.needsFoundry !== 'boolean') {
throw new Error(`needsFoundry must be boolean for id "${p.id}"`);
}
if (ids.has(p.id)) {
throw new Error(`Duplicate id: ${p.id}`);
}
ids.add(p.id);
if (workspaces.has(p.workspace)) {
throw new Error(`Duplicate workspace: ${p.workspace}`);
}
workspaces.add(p.workspace);
}
}

function main() {
const packages = loadConfig();
validateEntries(packages);

const include = [];
const out = process.env.GITHUB_OUTPUT;
if (!out) {
throw new Error('GITHUB_OUTPUT is not set');
}

for (const p of packages) {
const localVer = readLocalVersion(p.versionFile);
const npmVer = npmViewVersion(p.workspace);
const needsPublish = !npmVer || localVer !== npmVer;

console.log(`${p.id}: package.json=${localVer} npm=${npmVer || '<not published>'}`);

if (needsPublish) {
include.push({
id: p.id,
workspace: p.workspace,
versionFile: p.versionFile,
tagPrefix: p.tagPrefix,
needsFoundry: p.needsFoundry,
check: p.check,
});
}
}

const hasPublish = include.length > 0;
fs.appendFileSync(out, `has_publish=${hasPublish ? 'true' : 'false'}\n`);

const matrixJson = JSON.stringify(include);
const delim = 'PUBLISH_MATRIX_JSON';
fs.appendFileSync(out, `matrix<<${delim}\n${matrixJson}\n${delim}\n`);

if (hasPublish) {
console.log('Publish matrix will run for packages ahead of npm.');
} else {
console.log('Nothing to publish — versions on main already match npm for all configured packages.');
}
}

main();
130 changes: 130 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Publish

on:
pull_request:
types: [closed]
branches:
- main
workflow_dispatch:

permissions:
contents: read

jobs:
plan:
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'changeset-release/'))
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.decide.outputs.matrix }}
has_publish: ${{ steps.decide.outputs.has_publish }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
submodules: recursive

- name: Fail if pending changesets remain
run: |
set -euo pipefail
PENDING=""
for f in .changeset/*.md; do
[ -e "$f" ] || continue
case "$f" in
.changeset/README.md) continue ;;
*) PENDING="${PENDING}${f}"$'\n' ;;
esac
done
if [ -n "${PENDING}" ]; then
echo "::error::Merge the chore(release) version bump PR first. Pending changeset files:"
echo "${PENDING}"
exit 1
fi

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
registry-url: https://registry.npmjs.org

- name: Compare versions to npm
id: decide
run: node .github/scripts/publish-plan.js

- name: Publish plan summary
run: |
if [ "${{ steps.decide.outputs.has_publish }}" = "true" ]; then
echo "Publish jobs will run for packages ahead of npm."
else
echo "Nothing to publish — versions on main already match npm for all configured packages."
fi

publish:
needs: plan
if: needs.plan.outputs.has_publish == 'true'
runs-on: ubuntu-latest
environment: npm-publish
permissions:
contents: write
id-token: write
strategy:
matrix:
include: ${{ fromJSON(needs.plan.outputs.matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
submodules: recursive

- name: Install Foundry
if: matrix.needsFoundry == true
uses: foundry-rs/foundry-toolchain@v1

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
registry-url: https://registry.npmjs.org
cache: npm
cache-dependency-path: package-lock.json

- name: Install dependencies
run: npm ci

- name: Quality checks
run: ${{ matrix.check }}

- name: Publish to npm
run: npm publish -w ${{ matrix.workspace }} --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: "false"

- name: Create git tag and GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION_FILE: ${{ matrix.versionFile }}
TAG_PREFIX: ${{ matrix.tagPrefix }}
run: |
set -euo pipefail
VERSION=$(jq -r .version "$VERSION_FILE")
TAG="${TAG_PREFIX}@${VERSION}"
git fetch --tags --force
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists locally, skipping tag push."
elif git ls-remote --tags origin "refs/tags/${TAG}" | grep -q .; then
echo "Tag $TAG already exists on remote, skipping tag push."
else
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag "$TAG" HEAD
git push origin "refs/tags/$TAG"
fi
if gh release view "$TAG" >/dev/null 2>&1; then
echo "GitHub Release for $TAG already exists, skipping."
else
gh release create "$TAG" --title "$TAG" --generate-notes
fi
70 changes: 70 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Release

on:
workflow_dispatch:

permissions:
contents: read

jobs:
quality-gates:
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
cache: npm
cache-dependency-path: package-lock.json

- name: Install dependencies
run: npm ci

- name: Run all packages quality checks
run: npm run check

version-packages:
runs-on: ubuntu-latest
needs: quality-gates
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
submodules: recursive

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
cache: npm
cache-dependency-path: package-lock.json

- name: Install dependencies
run: npm ci

- name: Create or update version PR
uses: changesets/action@v1
with:
version: npm run version-packages
commit: "chore(release): apply changesets and bump versions"
title: "chore(release): bump npm versions & changelogs"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Loading
Loading