diff --git a/.releaserc.json b/.releaserc.json index 1896dca..11f8dd1 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -1,5 +1,6 @@ { "branches": ["release"], + "extends": "semantic-release-monorepo", "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", diff --git a/docs/release-process.md b/docs/release-process.md new file mode 100644 index 0000000..6ede339 --- /dev/null +++ b/docs/release-process.md @@ -0,0 +1 @@ +# Release Process with semantic-release-monorepo\n\n## Overview\n\nThis project uses `semantic-release-monorepo` to automate the versioning and release process across all packages in the monorepo. This ensures that each package is versioned independently based on its own changes, while maintaining a consistent release process.\n\n## How It Works\n\n1. When code is pushed to the `release` branch, the GitHub Actions workflow is triggered.\n2. The workflow builds and tests all packages.\n3. `semantic-release-monorepo` analyzes the commit history for each package to determine the next version.\n4. New versions are published to npm, and release notes are generated based on conventional commits.\n5. Git tags are created for each package release.\n\n## Commit Message Format\n\nThis project follows the [Conventional Commits](https://www.conventionalcommits.org/) specification. Your commit messages should be structured as follows:\n\n```\n[optional scope]: \n\n[optional body]\n\n[optional footer(s)]\n```\n\nExamples:\n\n```\nfeat(cli): add new command for project initialization\nfix(agent): resolve issue with async tool execution\ndocs: update installation instructions\n```\n\n### Types\n\n- `feat`: A new feature (triggers a minor version bump)\n- `fix`: A bug fix (triggers a patch version bump)\n- `docs`: Documentation changes\n- `style`: Changes that don't affect the code's meaning (formatting, etc.)\n- `refactor`: Code changes that neither fix a bug nor add a feature\n- `perf`: Performance improvements\n- `test`: Adding or correcting tests\n- `chore`: Changes to the build process, tooling, etc.\n\n### Breaking Changes\n\nIf your commit introduces a breaking change, add `BREAKING CHANGE:` in the footer followed by a description of the change. This will trigger a major version bump.\n\nExample:\n\n```\nfeat(agent): change API for tool execution\n\nBREAKING CHANGE: The tool execution API now requires an options object instead of individual parameters.\n```\n\n## Troubleshooting\n\nIf you encounter issues with the release process:\n\n1. Run `pnpm verify-release-config` to check if your semantic-release configuration is correct.\n2. Ensure your commit messages follow the conventional commits format.\n3. Check if the package has a `.releaserc.json` file that extends `semantic-release-monorepo`.\n4. Verify that each package has a `semantic-release` script in its `package.json`.\n\n## Manual Release\n\nIn rare cases, you might need to trigger a release manually. You can do this by:\n\n```bash\n# Release all packages\npnpm release\n\n# Release a specific package\ncd packages/cli\npnpm semantic-release\n```\n \ No newline at end of file diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000..19efdaf --- /dev/null +++ b/lerna.json @@ -0,0 +1 @@ +{\n \"version\": \"independent\",\n \"npmClient\": \"pnpm\",\n \"command\": {\n \"publish\": {\n \"conventionalCommits\": true,\n \"message\": \"chore(release): publish\"\n }\n },\n \"packages\": [\"packages/*\"]\n} \ No newline at end of file diff --git a/package.json b/package.json index de16847..e62cdd7 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "cli": "cd packages/cli && node --no-deprecation bin/cli.js", "prepare": "husky", "semantic-release": "semantic-release", - "release": "semantic-release" + "verify-release-config": "node scripts/verify-release-config.js", + "release": "pnpm verify-release-config && npx lerna exec --concurrency 1 -- npx --no-install semantic-release -e semantic-release-monorepo" }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ @@ -51,9 +52,11 @@ "eslint-plugin-promise": "^7.2.1", "eslint-plugin-unused-imports": "^4.1.4", "husky": "^9.1.7", + "lerna": "^8.2.1", "lint-staged": "^15.4.3", "prettier": "^3.5.1", "semantic-release": "^24.2.3", + "semantic-release-monorepo": "^8.0.2", "typescript-eslint": "^8.23.0" }, "pnpm": { diff --git a/packages/agent/.releaserc.json b/packages/agent/.releaserc.json new file mode 100644 index 0000000..9c41120 --- /dev/null +++ b/packages/agent/.releaserc.json @@ -0,0 +1,18 @@ +{ + "extends": "semantic-release-monorepo", + "branches": ["release"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + "@semantic-release/npm", + [ + "@semantic-release/git", + { + "assets": ["package.json", "CHANGELOG.md"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ], + "@semantic-release/github" + ] +} diff --git a/packages/agent/package.json b/packages/agent/package.json index 0f72bb4..74eca81 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -29,7 +29,8 @@ "typecheck": "tsc --noEmit", "clean": "rimraf dist", "clean:all": "rimraf node_modules dist", - "prepublishOnly": "pnpm run clean && pnpm run build && pnpm run test" + "prepublishOnly": "pnpm run clean && pnpm run build && pnpm run test", + "semantic-release": "semantic-release -e semantic-release-monorepo" }, "keywords": [ "ai", diff --git a/packages/cli/.releaserc.json b/packages/cli/.releaserc.json new file mode 100644 index 0000000..9c41120 --- /dev/null +++ b/packages/cli/.releaserc.json @@ -0,0 +1,18 @@ +{ + "extends": "semantic-release-monorepo", + "branches": ["release"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + "@semantic-release/npm", + [ + "@semantic-release/git", + { + "assets": ["package.json", "CHANGELOG.md"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ], + "@semantic-release/github" + ] +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 231410c..402c04f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -29,7 +29,8 @@ "test:ci": "vitest --run --coverage", "changeset": "changeset", "version": "changeset version", - "prepublishOnly": "pnpm run clean && pnpm run build && pnpm run test" + "prepublishOnly": "pnpm run clean && pnpm run build && pnpm run test", + "semantic-release": "semantic-release -e semantic-release-monorepo" }, "keywords": [ "ai", diff --git a/scripts/verify-release-config.js b/scripts/verify-release-config.js new file mode 100755 index 0000000..c83ae8d --- /dev/null +++ b/scripts/verify-release-config.js @@ -0,0 +1,83 @@ +#!/usr/bin/env node + +/* eslint-disable no-console */ +/* eslint-env node */ + +import fs from 'fs'; +import path from 'path'; + +const ROOT_DIR = path.resolve('.'); +const PACKAGES_DIR = path.join(ROOT_DIR, 'packages'); + +console.log('Starting verification script...'); +console.log('Checking root package.json...'); +// Check if required packages are installed +const rootPackageJson = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8')); +const hasSemanticReleaseMonorepo = rootPackageJson.devDependencies && 'semantic-release-monorepo' in rootPackageJson.devDependencies; +const hasLerna = rootPackageJson.devDependencies && 'lerna' in rootPackageJson.devDependencies; + +if (!hasSemanticReleaseMonorepo) { + console.error('❌ semantic-release-monorepo is not installed in root package.json'); + process.exit(1); +} + +if (!hasLerna) { + console.error('❌ lerna is not installed in root package.json'); + process.exit(1); +} + +console.log('Checking root .releaserc.json...'); +// Check root .releaserc.json +const rootReleaseRc = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, '.releaserc.json'), 'utf8')); +if (!rootReleaseRc.extends || rootReleaseRc.extends !== 'semantic-release-monorepo') { + console.error('❌ Root .releaserc.json does not extend semantic-release-monorepo'); + process.exit(1); +} + +console.log('Checking lerna.json...'); +// Check lerna.json +if (!fs.existsSync(path.join(ROOT_DIR, 'lerna.json'))) { + console.error('❌ lerna.json is missing'); + process.exit(1); +} + +console.log('Checking packages...'); +// Check packages +const packages = fs.readdirSync(PACKAGES_DIR) + .filter(dir => fs.statSync(path.join(PACKAGES_DIR, dir)).isDirectory()); + +console.log(`Found packages: ${packages.join(', ')}`); + +for (const pkg of packages) { + const packageDir = path.join(PACKAGES_DIR, pkg); + const packageJsonPath = path.join(packageDir, 'package.json'); + const releaseRcPath = path.join(packageDir, '.releaserc.json'); + + console.log(`Checking package ${pkg}...`); + + // Check package.json + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + if (!packageJson.scripts || !packageJson.scripts['semantic-release']) { + console.error(`❌ Package ${pkg} does not have a semantic-release script`); + process.exit(1); + } + } else { + console.error(`❌ Package ${pkg} does not have a package.json file`); + process.exit(1); + } + + // Check .releaserc.json + if (fs.existsSync(releaseRcPath)) { + const releaseRc = JSON.parse(fs.readFileSync(releaseRcPath, 'utf8')); + if (!releaseRc.extends || releaseRc.extends !== 'semantic-release-monorepo') { + console.error(`❌ Package ${pkg} .releaserc.json does not extend semantic-release-monorepo`); + process.exit(1); + } + } else { + console.error(`❌ Package ${pkg} does not have a .releaserc.json file`); + process.exit(1); + } +} + +console.log('✅ All semantic-release-monorepo configurations are correct!'); \ No newline at end of file