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
11 changes: 0 additions & 11 deletions .changeset/config.json

This file was deleted.

17 changes: 2 additions & 15 deletions .github/workflows/check-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,5 @@ jobs:
- name: Install dependencies
run: yarn install --immutable

- name: Run checks (common)
working-directory: ./common
run: yarn check && yarn build

- name: Run checks (gallery)
working-directory: ./gallery
run: yarn check

- name: Run checks (theme-modern)
working-directory: ./themes/modern
run: yarn lint && yarn format

- name: Run tests
working-directory: ./gallery
run: yarn test
- name: Run CI checks
run: yarn ci
54 changes: 29 additions & 25 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,49 @@
name: Release
name: Publish packages

on:
push:
branches: [main]
tags:
- "v*.*.*"

concurrency:
group: release-${{ github.ref }}
group: npm-publish-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: write
pull-requests: write
id-token: write

jobs:
release:
name: Version or publish packages
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write

steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check out repository
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v4
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: "yarn"
cache-dependency-path: "yarn.lock"
node-version: 24
package-manager-cache: false
registry-url: "https://registry.npmjs.org"

- name: Enable Corepack
run: corepack enable
- name: Set up Yarn
run: |
corepack enable
corepack prepare yarn@4.9.3 --activate

- name: Install dependencies
run: yarn install --immutable

- name: Create release PR or publish
uses: changesets/action@v1
with:
version: yarn version-packages
publish: yarn release
- name: Verify release tag and package artifacts
env:
RELEASE_TAG: ${{ github.ref_name }}
run: yarn release:check --version "${RELEASE_TAG#v}"
Comment thread
haltakov marked this conversation as resolved.

- name: Run CI checks
run: yarn ci

- name: Publish packages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.ref_name }}
run: yarn release --version "${RELEASE_TAG#v}"
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Agent Instructions

## Release And Publish Flow

When publishing the `@simple-photo-gallery/common`, `@simple-photo-gallery/theme-modern`, and `simple-photo-gallery` packages, use the GitHub Actions CI flow. Do not run `npm publish` manually from a local shell, and do not ask for an npm OTP for these packages.

The publish workflow lives in `.github/workflows/release.yml` and is triggered by tags matching `v*.*.*`. All three packages are released together. The workflow verifies that every package version matches the tag, runs the repository CI checks and a package dry-run, then publishes the packages to npm in dependency order.

Use this sequence for releases:

1. Check the registry versions with `npm view @simple-photo-gallery/common version`, `npm view @simple-photo-gallery/theme-modern version`, and `npm view simple-photo-gallery version`.
2. Bump `common/package.json`, `themes/modern/package.json`, and `gallery/package.json` to the same next patch, minor, or major version.
3. Run `yarn ci` and `yarn release:check --version <version>` from the repository root.
4. Commit the release changes.
5. Push the release commit to `main`.
6. Create and push the matching version tag, for example `git tag v2.2.2 && git push origin v2.2.2`.
7. Verify all three packages on npm before updating downstream applications.
10 changes: 3 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
"private": true,
"scripts": {
"gallery": "yarn workspace simple-photo-gallery gallery",
"changeset": "changeset",
"version-packages": "changeset version",
"ci": "yarn workspace @simple-photo-gallery/common check && yarn workspace @simple-photo-gallery/common build && yarn workspace simple-photo-gallery check && yarn workspace @simple-photo-gallery/theme-modern check && yarn workspace simple-photo-gallery test",
"release": "node scripts/release.mjs",
"release:dry-run": "node scripts/release.mjs --dry-run"
"release:check": "node scripts/release.mjs --dry-run"
},
"workspaces": {
"packages": [
Expand All @@ -19,8 +18,5 @@
"engines": {
"node": ">=20.0.0"
},
"packageManager": "yarn@4.9.3",
"devDependencies": {
"@changesets/cli": "^2.31.0"
}
"packageManager": "yarn@4.9.3"
}
112 changes: 44 additions & 68 deletions scripts/release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,28 @@ const dependencyFields = [

const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const options = parseOptions(process.argv.slice(2));

if (!options.dryRun && !options.version) {
throw new Error(
"Publishing requires --version. Use the version-tagged GitHub Actions workflow.",
);
}

const rootPackageJson = readJson(join(rootDir, "package.json"));
const changesetConfig = readJson(join(rootDir, ".changeset", "config.json"));
const packages = sortPackages(loadWorkspacePackages(rootPackageJson));
const archiveDir = mkdtempSync(join(tmpdir(), "simple-photo-gallery-release-"));

if (options.version) {
validatePackageVersions(packages, options.version);
}

let releasedPackages = 0;

for (const pkg of packages) {
const tagName = `${pkg.packageJson.name}@${pkg.packageJson.version}`;

if (!options.dryRun && packageVersionExists(pkg.packageJson)) {
console.log(
`${pkg.packageJson.name}@${pkg.packageJson.version} is already published on npm`,
);
createTagIfNeeded(tagName, { dryRun: false, emitNewTag: true });
continue;
}

Expand Down Expand Up @@ -59,17 +66,18 @@ for (const pkg of packages) {
"publish",
archivePath,
"--access",
options.access ?? changesetConfig.access ?? "public",
options.access ?? "public",
"--tag",
options.tag,
]);

createTagIfNeeded(tagName, { dryRun: false, emitNewTag: true });
releasedPackages += 1;
}

if (options.dryRun) {
console.log(`Dry run complete. Packed ${packages.length} publishable packages.`);
console.log(
`Dry run complete. Packed ${packages.length} publishable packages.`,
);
} else if (releasedPackages === 0) {
console.log("No unpublished packages were published.");
}
Expand All @@ -79,6 +87,7 @@ function parseOptions(args) {
access: undefined,
dryRun: false,
tag: "latest",
version: undefined,
};

for (let index = 0; index < args.length; index += 1) {
Expand All @@ -101,6 +110,12 @@ function parseOptions(args) {
continue;
}

if (arg === "--version") {
parsed.version = readOptionValue(args, index, arg);
index += 1;
continue;
}

throw new Error(`Unknown release option: ${arg}`);
}

Expand All @@ -117,6 +132,28 @@ function readOptionValue(args, index, name) {
return value;
}

function validatePackageVersions(workspacePackages, expectedVersion) {
const mismatches = workspacePackages.filter(
(pkg) => pkg.packageJson.version !== expectedVersion,
);

if (mismatches.length === 0) {
console.log(
`All package versions match release version ${expectedVersion}.`,
);
return;
}

const details = mismatches
.map(
(pkg) =>
`- ${pkg.packageJson.name}: ${pkg.packageJson.version} (expected ${expectedVersion})`,
)
.join("\n");

throw new Error(`Package versions do not match the release tag:\n${details}`);
}

function loadWorkspacePackages(packageJson) {
const workspacePatterns = Array.isArray(packageJson.workspaces)
? packageJson.workspaces
Expand Down Expand Up @@ -248,67 +285,6 @@ function validatePackedManifest(archivePath, pkg) {
}
}

function createTagIfNeeded(tagName, { dryRun, emitNewTag }) {
if (remoteTagExists(tagName)) {
console.log(`Tag already exists on origin: ${tagName}`);
return;
}

if (dryRun) {
console.log(`[dry-run] Would create tag ${tagName}`);
return;
}

if (localTagExists(tagName)) {
ensureLocalTagPointsAtHead(tagName);
} else {
run("git", ["tag", tagName]);
}

if (emitNewTag) {
console.log(`New tag: ${tagName}`);
}
}

function remoteTagExists(tagName) {
const result = spawnSync(
"git",
["ls-remote", "--exit-code", "--tags", "origin", `refs/tags/${tagName}`],
{
cwd: rootDir,
encoding: "utf8",
},
);

return result.status === 0;
}

function localTagExists(tagName) {
const result = spawnSync("git", ["rev-parse", "--verify", tagName], {
cwd: rootDir,
encoding: "utf8",
});

return result.status === 0;
}

function ensureLocalTagPointsAtHead(tagName) {
const head = execFileSync("git", ["rev-parse", "HEAD"], {
cwd: rootDir,
encoding: "utf8",
}).trim();
const taggedCommit = execFileSync("git", ["rev-list", "-n", "1", tagName], {
cwd: rootDir,
encoding: "utf8",
}).trim();

if (head !== taggedCommit) {
throw new Error(
`Local tag ${tagName} exists but points at ${taggedCommit}, not HEAD ${head}`,
);
}
}

function readJson(path) {
return JSON.parse(readFileSync(path, "utf8"));
}
Expand Down
Loading
Loading