Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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
Comment thread
jannik-stacks marked this conversation as resolved.
Resolved

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)
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
Comment thread
jannik-stacks marked this conversation as resolved.
Resolved
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["@stacks/*"]],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
12 changes: 5 additions & 7 deletions .github/DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
## Adding dependencies

This repo uses Lerna [hoisting](https://github.com/lerna/lerna/blob/main/doc/hoist.md) for package dependencies.
This repo uses [npm workspaces](https://docs.npmjs.com/cli/v10/using-npm/workspaces) for package management.

In order to install a new dependency to a package, the [`lerna add`](https://github.com/lerna/lerna/tree/main/commands/add) command must be used, rather than `npm install <package>`.

For example, the following command installs `lodash` as a dependency to the `@stacks/storage` package:
To install a new dependency to a specific package, use the `-w` (workspace) flag:

```shell
# Run within the root directory
npx lerna add lodash --scope @stacks/storage
npm install lodash -w @stacks/storage
```

Add `--dev` to install as a development dependency:
Add `--save-dev` to install as a development dependency:

```shell
npx lerna add lodash --scope @stacks/storage --dev
npm install lodash -w @stacks/storage --save-dev
```
73 changes: 73 additions & 0 deletions .github/scripts/snapshot-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env node
// Rewrite packages/*/package.json to a snapshot version like
// `<base>-<tag>.<counter>` (or `<base>-pr.<num>.<counter>`). Run between
// `changeset version` and `changeset publish --tag <tag>`.
//
// Usage: node .github/scripts/snapshot-version.mjs <beta|pr> [--pr <number>]
Comment thread
jannik-stacks marked this conversation as resolved.
Resolved

import { readFile, writeFile, readdir, appendFile } from 'node:fs/promises';
import { execFileSync } from 'node:child_process';

const [tag, , prNumber] = process.argv.slice(2);
if (!['beta', 'pr'].includes(tag) || (tag === 'pr' && !prNumber)) {
console.error('Usage: snapshot-version.mjs <beta|pr> [--pr <number>]');
process.exit(1);
}

const DEP_TYPES = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];

const entries = await readdir('packages', { withFileTypes: true });
const pkgs = (await Promise.all(
entries
.filter((e) => e.isDirectory())
.map((e) => `packages/${e.name}/package.json`)
.map(async (file) => {
try { return { file, json: JSON.parse(await readFile(file, 'utf8')) }; }
catch { return null; }
}),
)).filter(Boolean);

const rep = pkgs.find(({ json }) => json.name && !json.private && /^\d+\.\d+\.\d+$/.test(json.version));
if (!rep) {
console.error('No non-private workspace package with plain semver — did you run `changeset version` first?');
process.exit(1);
}

const prefix = tag === 'beta' ? `${rep.json.version}-beta.` : `${rep.json.version}-pr.${prNumber}.`;
const version = `${prefix}${nextCounter(rep.json.name, prefix)}`;
console.log(`Snapshot version: ${version} (base from ${rep.json.name})`);

const internals = new Set(pkgs.map(({ json }) => json.name).filter(Boolean));
const named = pkgs.filter(({ json }) => json.name);

await Promise.all(named.map(({ file, json }) =>
writeFile(file, JSON.stringify(rewrite(json, version, internals), null, 2) + '\n'),
));
named.forEach(({ json }) => console.log(` ${json.name} → ${version}`));

if (process.env.GITHUB_OUTPUT) await appendFile(process.env.GITHUB_OUTPUT, `version=${version}\n`);

function rewrite(json, version, internals) {
const rewriteDeps = (deps) =>
Object.fromEntries(Object.entries(deps).map(([n, v]) => [n, internals.has(n) ? version : v]));
return DEP_TYPES.reduce(
(acc, dt) => (acc[dt] ? { ...acc, [dt]: rewriteDeps(acc[dt]) } : acc),
{ ...json, version },
);
}

function nextCounter(name, prefix) {
let raw;
try {
raw = execFileSync('npm', ['view', name, 'versions', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
} catch (e) {
if ((e.stderr || '').includes('E404')) return 0;
throw e;
}
const all = JSON.parse(raw);
const counters = (Array.isArray(all) ? all : [all])
.filter((v) => v.startsWith(prefix))
.map((v) => Number(v.slice(prefix.length)))
.filter(Number.isInteger);
return counters.length ? Math.max(...counters) + 1 : 0;
}
162 changes: 130 additions & 32 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,82 +1,180 @@
name: publish

# All npm-publishing flows live in this single file. npm's trusted-publisher
# config for every `@stacks/*` package points at `publish.yml` — renaming this
# file requires updating that config on npmjs.com per package.
#
# release — push to main / workflow_dispatch.
# changesets/action opens/updates the "chore: version packages"
# PR. When that PR is merged, the same action publishes the
# bumped packages to npm under `latest`. `commitMode: github-api`
# gives commits a Verified badge without signing keys in CI.
#
# beta — push to main.
# Snapshot-publishes every push to `@beta`, with versions of the
# form `<next-release>-beta.<N>` (counter scoped to the base
# version, queried from npm).
#
# pr — pull_request.
# Same as `beta` but `@pr` dist-tag and versions of the form
# `<next-release>-pr.<PR#>.<N>`. Sticky-header'd into the PR
# description so reviewers can install the canary build.
#
# Forks never reach the publish step (`is-not-fork` gate from pre-run).

on:
push:
branches:
- main
tags-ignore:
- "**"
- '**'
paths-ignore:
- "**.md"
- '**.md'

workflow_dispatch:
inputs:
pr-number:
description: 'PR number to publish a canary build for (leave blank for a normal release dispatch)'
Comment thread
jannik-stacks marked this conversation as resolved.
Resolved
type: string
required: false
default: ''

permissions:
id-token: write # Required for OIDC
contents: read
contents: write
pull-requests: write
id-token: write # npm OIDC trusted publishing

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
pre-run:
uses: ./.github/workflows/pre-run.yml
secrets: inherit

publish:
release:
runs-on: ubuntu-latest
needs: pre-run
if: needs.pre-run.outputs.is-not-fork == 'true' && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.pr-number == ''))
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: npm

- run: npm ci

- name: Create release PR or publish
uses: changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b # v1.8.0
Comment thread
jannik-stacks marked this conversation as resolved.
Resolved
with:
version: npm run version
publish: npm run release
title: 'chore: version packages'
commit: 'chore: version packages'
commitMode: github-api
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

beta:
runs-on: ubuntu-latest
needs: pre-run
if: "needs.pre-run.outputs.is-not-fork == 'true' && (github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, 'chore: version packages'))"
if: needs.pre-run.outputs.is-not-fork == 'true' && github.event_name == 'push'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: npm

- run: npm ci
- run: npm run build

- run: git config --local user.email 'action@github.com'
- run: git config --local user.name 'GitHub Action'
- name: Seed dummy changeset if none present
run: |
if ! ls .changeset/*.md 2>/dev/null | grep -vqi readme; then
cat > .changeset/__snapshot.md <<'EOF'
---
"@stacks/common": patch
---

snapshot
EOF
fi

- name: Publish
- run: npx changeset version
- run: node .github/scripts/snapshot-version.mjs beta

# CJS+ESM only — UMD has a known module-resolution bug after the
# snapshot rewrite. Per-package failures are non-fatal here; whatever
# builds gets published.
- name: Build CJS+ESM per workspace (skip UMD)
run: |
npx lerna publish from-package --no-verify-access --yes
RELEASE=$(cat lerna.json | jq -r '.version')
git tag -a v$RELEASE -m v$RELEASE
git push origin v$RELEASE
for pkg in packages/*/; do
[ -f "$pkg/package.json" ] || continue
( cd "$pkg" && npm run build:cjs --if-present && npm run build:esm --if-present ) || echo "build failed for $pkg (continuing)"
done

# NPM_CONFIG_IGNORE_SCRIPTS skips each package's prepublishOnly hook
# (which would re-run the UMD build and fail).
- run: npx changeset publish --tag beta --no-git-tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_IGNORE_SCRIPTS: 'true'

publish-beta:
pr:
runs-on: ubuntu-latest
needs: pre-run
if: "needs.pre-run.outputs.is-not-fork == 'true' && !contains(github.event.head_commit.message, 'chore: version packages')"
if: needs.pre-run.outputs.is-not-fork == 'true' && github.event_name == 'workflow_dispatch' && inputs.pr-number != ''
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

# pulls all tags (needed for lerna to correctly version)
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: npm

- run: npm ci

- run: git config --local user.email 'action@github.com'
- run: git config --local user.name 'GitHub Action'
- name: Seed dummy changeset if none present
run: |
if ! ls .changeset/*.md 2>/dev/null | grep -vqi readme; then
cat > .changeset/__snapshot.md <<'EOF'
---
"@stacks/common": patch
---

snapshot
EOF
fi
Comment thread
jannik-stacks marked this conversation as resolved.
Resolved

- run: npx changeset version
- id: snapshot
run: node .github/scripts/snapshot-version.mjs pr --pr ${{ inputs.pr-number }}

- name: Version & Publish
- name: Build CJS+ESM per workspace (skip UMD)
run: |
LATEST=$(git describe --tags --abbrev=0)
npx lerna version $LATEST --force-publish --no-changelog --no-push --no-git-tag-version --yes
git commit -a -m 'DONT PUSH: fast-forward to latest npm release' --allow-empty --no-verify
npx lerna version --force-publish --no-changelog --conventional-commits --conventional-prerelease --preid beta --no-push --no-git-tag-version --yes
git commit -a -m 'DONT PUSH: version beta release' --allow-empty --no-verify
RELEASE=$(cat lerna.json | jq -r '.version')
npx lerna publish from-package --no-verify-access --pre-dist-tag beta --yes
git reset HEAD~2 --hard
git tag v$RELEASE
git push origin v$RELEASE
for pkg in packages/*/; do
[ -f "$pkg/package.json" ] || continue
( cd "$pkg" && npm run build:cjs --if-present && npm run build:esm --if-present ) || echo "build failed for $pkg (continuing)"
done

- run: npx changeset publish --tag pr --no-git-tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_IGNORE_SCRIPTS: 'true'

- name: Update PR sticky header
uses: janniks/pull-request-fixed-header@57bc082ecbd040df344747d5b8644af5fad02c95 # v1.2.0
with:
header: |
> This PR was published to npm with the version `${{ steps.snapshot.outputs.version }}` (dist-tag `pr`).
> e.g. `npm install @stacks/common@${{ steps.snapshot.outputs.version }} --save-exact`
pr-number: ${{ inputs.pr-number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 changes: 0 additions & 51 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,54 +26,3 @@ jobs:
needs: pre-run
uses: ./.github/workflows/tests.yml
secrets: inherit

# FOR NOW DISABLE PUBLISHING TO NPM ON EACH PULL-REQUEST PUSH
# publish-pr:
# runs-on: ubuntu-latest
# needs: [pre-run, tests]
# if: needs.pre-run.outputs.is-not-fork == 'true'
# steps:
# - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# if: github.event_name == 'pull_request'
# with:
# ref: ${{ github.event.pull_request.head.ref }}
# fetch-depth: 0
# - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# if: github.event_name == 'workflow_dispatch'
# with:
# fetch-depth: 0
# - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
# with:
# node-version: 24
# cache: npm
# - run: npm install -g npm@latest && npm ci
# - name: Set up npm auth token
# run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
# env:
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# - name: Publish to npm (feature branch)
# id: publish-pr
# run: |
# PUBLISH_OUTPUT_FILE=$(mktemp)
# npx lerna publish --canary --force-publish --preid pr --dist-tag pr --no-verify-access --no-push --no-git-tag-version --yes | tee $PUBLISH_OUTPUT_FILE
# VERSION=$(grep -oP '@stacks/common\s*=>\s*\K[^\s]+' $PUBLISH_OUTPUT_FILE | head -n 1)
# echo "Canary version: $VERSION"
# echo "::set-output name=version::$VERSION"
# if: ${{ (github.head_ref || github.ref_name) != 'nakamoto' && (github.head_ref || github.ref_name) != 'next' }}
# env:
# SKIP_TESTS: true
# - name: Publish to npm (next/nakamoto)
# run: |
# git reset --hard
# npx lerna publish --canary major --force-publish --preid $BRANCH --dist-tag $BRANCH --no-verify-access --no-push --no-git-tag-version --yes
# if: ${{ (github.head_ref || github.ref_name) == 'nakamoto' || (github.head_ref || github.ref_name) == 'next' }}
# env:
# BRANCH: ${{ github.head_ref || github.ref_name }}
# SKIP_TESTS: true
# - name: Edit pull-request description with published version
# uses: janniks/pull-request-fixed-header@v1.0.1
# with:
# header: "> This PR was published to npm with the version `${{ steps.publish-pr.outputs.version || env.BRANCH }}`\n> e.g. `npm install @stacks/common@${{ steps.publish-pr.outputs.version || env.BRANCH }} --save-exact`"
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# env:
# BRANCH: ${{ github.head_ref || github.ref_name }}
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
run: npm run madge

- name: Run tests
run: npm run lerna run test --stream --parallel -- -- --coverage
run: npm run test --workspaces --if-present -- --coverage

- name: Upload test coverage
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2
Expand Down
Loading
Loading