diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..dbc2b5e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +## Change + +Describe the problem and resulting behavior. Target `next` and use a Conventional Commit title, for example `fix(cli): handle missing snapshots`. + +## Validation + +List the relevant checks and their results. + +## Release impact + +Describe any compatibility change. Tagging or publishing requires Omer's explicit approval; a PR is not release approval. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a02ba4..73a9537 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,142 +1,43 @@ name: Continuous Integration -on: pull_request - +on: + pull_request: + branches: [next] + push: + branches: [next] permissions: - id-token: write contents: read - actions: read - checks: write - pull-requests: write - +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: - build: - name: Build + verify: + name: Verify (Node ${{ matrix.node }}) runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - node-version: [20.x, 22.x, 24.x] + node: [22, 24, 26] steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Use Node ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.15.4 - - - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22.x' - - - name: Install pnpm - uses: pnpm/action-setup@v4 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 with: version: 9.15.4 - - - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Lint - run: pnpm lint - - test: - name: Unit Tests + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm build + - run: pnpm lint + - run: pnpm test + - run: pnpm test:e2e + ci: + name: CI + if: always() + needs: [verify] runs-on: ubuntu-latest - strategy: - matrix: - project: ['cli', 'governance', 'changesets', 'json-schema-differ', 'types'] steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22.x' - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.15.4 - - - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - - name: Create Coverage Directory - run: mkdir -p ${{ github.workspace }}/coverage - - - name: Test - run: pnpm lerna run test --scope @contractual/${{ matrix.project }} --stream - continue-on-error: true + - name: Require every verification job env: - COVERAGE_DIR: ${{ github.workspace }}/coverage - COVERAGE_FILE: coverage-${{ matrix.project }}.xml - - e2e: - name: E2E Tests - runs-on: ubuntu-latest - needs: [build] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22.x' - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9.15.4 - - - uses: actions/cache@v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }} - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - - name: Run E2E Tests - run: pnpm test:e2e + RESULT: ${{ needs.verify.result }} + run: test "$RESULT" = success diff --git a/.github/workflows/e2e-release.yml b/.github/workflows/e2e-release.yml index 9ab5fd0..6f3029b 100644 --- a/.github/workflows/e2e-release.yml +++ b/.github/workflows/e2e-release.yml @@ -29,7 +29,7 @@ jobs: fail-fast: false matrix: e2e-project: ['cli-basic', 'cli-lifecycle', 'packages-import'] - node-version: [20.x, 22.x, 24.x] + node-version: [22.x, 24.x, 26.x] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..31cdbe1 --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,39 @@ +name: Pull request title +on: + pull_request: + branches: [next] + types: [opened, edited, synchronize, reopened, ready_for_review] +permissions: + pull-requests: read +jobs: + title: + name: PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + types: | + feat + fix + docs + style + refactor + perf + test + build + ci + chore + revert + scopes: | + cli + changesets + types + governance + differs\.core + differs\.json-schema + differs\.openapi + \* + requireScope: false + subjectPattern: '^\S[^\r\n]*$' diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 7bb567e..d1bfb99 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -1,106 +1,73 @@ name: Publish Packages -env: - CI: true - -permissions: - id-token: write - contents: write - on: workflow_dispatch: inputs: - dist_tag: - description: 'Distribution Tag' - type: choice - options: - - 'next' - - 'latest' - - 'rc' - - 'dev' - - 'alpha' - - 'beta' + commit: + description: Full reviewed commit SHA on next, explicitly approved by Omer required: true - default: 'next' - target_branch: - description: 'Target branch to release from' + type: string + dist_tag: + description: Approved npm distribution tag type: choice - options: - - 'next' - - 'master' + options: [dev, next, rc, alpha, beta, latest] + default: dev required: true - default: 'next' - +permissions: + id-token: write + contents: read +concurrency: + group: release + cancel-in-progress: false jobs: publish: - name: Publish to NPM + if: github.ref == 'refs/heads/next' + environment: release runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.target_branch }} + ref: ${{ inputs.commit }} fetch-depth: 0 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22.x' - registry-url: https://registry.npmjs.org/ - scope: '@contractual' - always-auth: true - - - name: Install pnpm - uses: pnpm/action-setup@v4 + - name: Verify approved commit belongs to next + env: + APPROVED_COMMIT: ${{ inputs.commit }} + run: | + [[ "$APPROVED_COMMIT" =~ ^[0-9a-f]{40}$ ]] + test "$(git rev-parse HEAD)" = "$APPROVED_COMMIT" + git merge-base --is-ancestor HEAD origin/next + - uses: pnpm/action-setup@v4 with: version: 9.15.4 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm lerna run build - - - name: Capture Versions for Report - run: | - echo "## Publishing to NPM" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Registry:** \`https://registry.npmjs.org/\`" >> $GITHUB_STEP_SUMMARY - echo "**Dist Tag:** \`${{ github.event.inputs.dist_tag }}\`" >> $GITHUB_STEP_SUMMARY - echo "**Branch:** \`${{ github.event.inputs.target_branch }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Packages:" >> $GITHUB_STEP_SUMMARY - lerna ls --json | jq -r '.[] | "- **\(.name)** -> `v\(.version)`"' >> $GITHUB_STEP_SUMMARY - - - name: Publish Packages - run: | - npx lerna publish from-package --yes \ - --dist-tag ${{ github.event.inputs.dist_tag }} \ - --no-git-reset + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org/ + - run: pnpm install --frozen-lockfile + - run: pnpm build + - run: pnpm lint + - run: pnpm test + - run: pnpm test:e2e + - name: Check npm authorization and package metadata env: + DIST_TAG: ${{ inputs.dist_tag }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Generate Success Summary - if: success() - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## Publish Successful" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "All packages have been successfully published to npm!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Installation command:**" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY - echo "npm install @contractual/cli@${{ github.event.inputs.dist_tag }}" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - - - name: Generate Failure Summary - if: failure() run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## Publish Failed" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "The publish step failed. Check the logs above for details." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Common issues:**" >> $GITHUB_STEP_SUMMARY - echo "- Version already exists in npm registry" >> $GITHUB_STEP_SUMMARY - echo "- Authentication token is invalid or expired" >> $GITHUB_STEP_SUMMARY - echo "- Network connectivity issues" >> $GITHUB_STEP_SUMMARY + case "$DIST_TAG" in dev|next|rc|alpha|beta|latest) ;; *) exit 1 ;; esac + test -n "$NODE_AUTH_TOKEN" + npm_user="$(npm whoami --registry=https://registry.npmjs.org/)" + package_access="$(npm access list packages "$npm_user" --json)" + for manifest in packages/*/package.json; do + if jq -e '.private == true' "$manifest" > /dev/null; then continue; fi + package_name="$(jq -r '.name' "$manifest")" + jq -e '.repository.url == "https://github.com/codotech/contractual.git" and .publishConfig.access == "public"' "$manifest" > /dev/null + jq -e --arg name "$package_name" '.[$name] == "read-write"' <<< "$package_access" > /dev/null + if [ "$DIST_TAG" = latest ]; then + jq -e '.version | contains("-") | not' "$manifest" > /dev/null + fi + echo "$package_name: publishing preflight passed ($DIST_TAG)" + done + - name: Publish approved versions (does not create Git tags) + env: + DIST_TAG: ${{ inputs.dist_tag }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: pnpm exec lerna publish from-package --yes --dist-tag "$DIST_TAG" --no-git-reset diff --git a/.github/workflows/release-packages.yml b/.github/workflows/release-packages.yml index 43032a0..c068a23 100644 --- a/.github/workflows/release-packages.yml +++ b/.github/workflows/release-packages.yml @@ -3,117 +3,65 @@ on: workflow_dispatch: inputs: release_type: - description: 'Release Type' + description: Approved release type type: choice - options: - - 'graduate' - - 'prerelease' + options: [prerelease, graduate] + default: prerelease required: true - default: 'prerelease' - target_branch: - description: 'Target Branch' - type: choice - options: - - 'master' - - 'next' - required: true - default: 'next' preid: - description: 'Prefix Id' + description: Prerelease identifier type: choice - options: - - 'latest' - - 'next' - - 'rc' - - 'dev' - - 'alpha' - - 'beta' + options: [dev, next, rc, alpha, beta] + default: dev required: true - default: 'next' - exact_version: - description: 'Exact Version' - type: string - required: false - permissions: contents: write - id-token: write - + pull-requests: write +concurrency: + group: release + cancel-in-progress: false jobs: - tag-version: - name: Tag Version + prepare: + if: github.ref == 'refs/heads/next' + environment: release runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.target_branch }} + ref: next fetch-depth: 0 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: '22.x' - - - name: Install pnpm - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v4 with: version: 9.15.4 - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build - run: pnpm build - - - name: Config Git User - run: | - git config --global user.name "${{ github.actor }}" - git config --global user.email "${{ github.actor }}@users.noreply.github.com" - - - name: Prerelease Version (Exact Version) - if: ${{ github.event.inputs.release_type == 'prerelease' && github.event.inputs.exact_version }} - run: | - npx lerna version ${{ github.event.inputs.exact_version }} --yes --no-changelog - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Prerelease Version - if: ${{ github.event.inputs.release_type == 'prerelease' && !github.event.inputs.exact_version }} - run: | - npx lerna version --yes \ - --conventional-commits \ - --conventional-prerelease \ - --preid ${{ github.event.inputs.preid }} \ - --no-changelog - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Graduate Version - if: ${{ github.event.inputs.release_type == 'graduate' }} - run: npx lerna version --conventional-graduate --yes + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: pnpm install --frozen-lockfile + - run: pnpm build + - run: pnpm test + - run: pnpm test:e2e + - name: Prepare version changes without tags env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Push Changes to Branch + RELEASE_TYPE: ${{ inputs.release_type }} + PREID: ${{ inputs.preid }} run: | - git push origin ${{ github.event.inputs.target_branch }} --no-verify - git push origin --tags + git switch -c "chore/release-${GITHUB_RUN_ID}" + if [ "$RELEASE_TYPE" = graduate ]; then + pnpm exec lerna version --conventional-graduate --yes --no-git-tag-version --no-push --no-commit-hooks --allow-branch "chore/release-${GITHUB_RUN_ID}" + else + pnpm exec lerna version --conventional-commits --conventional-prerelease --preid "$PREID" --yes --no-git-tag-version --no-push --no-commit-hooks --allow-branch "chore/release-${GITHUB_RUN_ID}" + fi + - name: Open a release PR env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Generate Release Summary + GH_TOKEN: ${{ github.token }} run: | - echo "## Release Prepared" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Branch:** \`${{ github.event.inputs.target_branch }}\`" >> $GITHUB_STEP_SUMMARY - echo "**Release Type:** \`${{ github.event.inputs.release_type }}\`" >> $GITHUB_STEP_SUMMARY - echo "**Preid:** \`${{ github.event.inputs.preid }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Tagged Packages" >> $GITHUB_STEP_SUMMARY - git tag --sort=-creatordate | head -10 | while read tag; do - echo "- \`$tag\`" >> $GITHUB_STEP_SUMMARY - done - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Next Steps" >> $GITHUB_STEP_SUMMARY - echo "Run the **Publish Packages** workflow to publish to npm" >> $GITHUB_STEP_SUMMARY + if git diff --quiet; then + echo 'No version changes to prepare.' + exit 0 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add packages pnpm-lock.yaml + git commit -m 'chore: prepare package versions' + git push origin "HEAD:refs/heads/chore/release-${GITHUB_RUN_ID}" + gh pr create --base next --head "chore/release-${GITHUB_RUN_ID}" --title 'chore: prepare package versions' --body 'Review package versions and validation before merging. This PR does not tag or publish. Publishing requires a separate explicit approval from Omer. Because this PR uses GITHUB_TOKEN, a maintainer must reopen it to trigger PR checks.' diff --git a/.github/workflows/tag-global.yml b/.github/workflows/tag-global.yml index 31f955e..6723a1b 100644 --- a/.github/workflows/tag-global.yml +++ b/.github/workflows/tag-global.yml @@ -6,13 +6,18 @@ on: tag_name: description: 'Tag Name (e.g. v1.0.0)' required: true + commit: + description: 'Full reviewed commit SHA on next, explicitly approved by Omer' + required: true + type: string permissions: contents: write - id-token: write jobs: tag-and-push: + if: github.ref == 'refs/heads/next' + environment: release name: Tag and Push runs-on: ubuntu-latest @@ -20,22 +25,36 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 with: + ref: ${{ inputs.commit }} fetch-depth: 0 + - name: Verify approved commit + env: + APPROVED_COMMIT: ${{ inputs.commit }} + run: | + [[ "$APPROVED_COMMIT" =~ ^[0-9a-f]{40}$ ]] + test "$(git rev-parse HEAD)" = "$APPROVED_COMMIT" + git merge-base --is-ancestor HEAD origin/next + - name: Config Git run: | git config --global user.email "${{ github.actor }}@users.noreply.github.com" git config --global user.name "${{ github.actor }}" - name: Tag and Push + env: + TAG_NAME: ${{ inputs.tag_name }} run: | - git tag -a ${{ inputs.tag_name }} -m "Tag Version ${{ inputs.tag_name }}" - git push origin ${{ inputs.tag_name }} + git check-ref-format "refs/tags/$TAG_NAME" + git tag -a "$TAG_NAME" -m "Tag Version $TAG_NAME" + git push origin "refs/tags/$TAG_NAME" - name: Generate Summary + env: + TAG_NAME: ${{ inputs.tag_name }} run: | echo "## Tag Created" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "**Tag:** \`${{ inputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** \`$TAG_NAME\`" >> $GITHUB_STEP_SUMMARY echo "**Commit:** \`$(git rev-parse HEAD)\`" >> $GITHUB_STEP_SUMMARY echo "**Branch:** \`$(git rev-parse --abbrev-ref HEAD)\`" >> $GITHUB_STEP_SUMMARY diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9ac3f44 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,9 @@ +# Repository workflow + +- Start feature/fix branches from the latest `origin/next`; open PRs targeting `next`. +- Use Conventional Commit PR titles and commits, for example `fix(cli): handle missing snapshots`. +- Squash merge only after required CI and review. Never bypass the ruleset or push directly to `next`. +- Run `pnpm build`, `pnpm lint`, `pnpm test`, and `pnpm test:e2e` for release-related changes. +- Use existing GitHub Actions and native workflow commands; do not add helper scripts or custom package-verification actions. Manage rulesets directly in GitHub, not checked-in JSON copies. +- Always ask Omer for explicit approval before creating, moving, or pushing any version tag; publishing packages or GitHub Releases; changing npm distribution tags; or dispatching a workflow that performs those actions. Approval to fix code or open a PR does not authorize a release. +- Do not change package versions unless a release preparation was explicitly requested. Preserve existing untracked `docs/` material. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c7f8c1f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +# Contributing to Contractual + +All development targets `next`, the schema lifecycle implementation. `master` contains the previous code-generation product. + +```sh +git fetch origin +git switch -c fix/describe-the-change origin/next +pnpm install --frozen-lockfile +pnpm build +``` + +Use Node 22 or newer and pnpm 9.15.4. Before opening a PR, run: + +```sh +pnpm lint +pnpm test +pnpm test:e2e +``` + +These commands validate source and behavior. CI runs them on Node 22, 24, and 26. Normal CI never publishes or tags. Use existing GitHub Actions and native workflow commands, without a separate scripts folder or custom package-verification action. + +## Open a PR to next + +Use a Conventional Commit title: `fix(cli): handle missing snapshots`, `feat(differs.core): classify new constraints`, or `docs: clarify supported formats`. Supported types are `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, and `revert`. Use `!` before the colon for a breaking change. + +One approving review, resolved conversations, an up-to-date branch, and passing `CI` and `PR title` checks are required. PRs are squash merged, with the PR title used as the commit subject. The ruleset is managed directly in the repository's GitHub settings; no ruleset JSON file is needed. + +Scopes must match a workspace package: `cli`, `changesets`, `types`, `governance`, `differs.core`, `differs.json-schema`, or `differs.openapi`. Use `*` for changes across packages or omit the scope for repository-wide changes. The semantic PR action enforces this allowlist; update both its workflow configuration and the repository ruleset when adding or renaming a package. + +## Release approval + +Normal development never publishes packages or creates version tags. Ask Omer before tagging, publishing, changing npm distribution tags, or dispatching a release workflow. See [RELEASING.md](RELEASING.md) for the release procedure and current support boundary. diff --git a/README.md b/README.md index f21b5d8..addcde6 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,14 @@
-Schema contract lifecycle for OpenAPI, JSON Schema, and AsyncAPI
+Schema contract lifecycle for OpenAPI and JSON Schema
Linting • Breaking change detection • Versioning • Release automation
-Supported Formats: OpenAPI, JSON Schema, AsyncAPI +Supported Formats: OpenAPI, JSON Schema
## Features @@ -38,7 +38,7 @@ Linting • Breaking change detection • Versioning • Release automation - **CI Integration** - GitHub Action posts diff tables on PRs, auto-generates changesets, and opens Version PRs for release automation. -- **Format Agnostic** - Works with OpenAPI, JSON Schema, and AsyncAPI. Custom linters and differs can be configured per contract. +- **Format Agnostic** - Works with OpenAPI and JSON Schema. Custom linters and differs can be configured per contract. ## Quick Example @@ -78,15 +78,17 @@ Updated CHANGELOG.md ## Installation +Development releases are currently available under the npm `dev` tag. There is no stable release yet. Built-in linting and diffing support OpenAPI and JSON Schema; AsyncAPI and ODCS require custom engines. AI, fixed versioning, and generation hooks are planned. See [release scope](RELEASING.md) and [contributing](CONTRIBUTING.md). + ```bash -npm install -g @contractual/cli +npm install -g @contractual/cli@dev ``` Or with other package managers: ```bash -pnpm add -g @contractual/cli -yarn global add @contractual/cli +pnpm add -g @contractual/cli@dev +yarn global add @contractual/cli@dev ``` ## Getting Started @@ -102,7 +104,7 @@ yarn global add @contractual/cli ## Community - [Documentation](https://contractual.dev) -- [GitHub Issues](https://github.com/contractual-dev/contractual/issues) +- [GitHub Issues](https://github.com/codotech/contractual/issues) ## License diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..f71e0f4 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,23 @@ +# Releasing Contractual + +Releases require Omer's explicit approval. Fixes and merged PRs do not authorize tagging or publishing. The `release` environment requires Omer's approval for release jobs. + +## Supported release scope + +The first stable scope is OpenAPI 3.0/3.1 and JSON Schema linting and diffing, changesets, independent versioning, and the GitHub Action. AsyncAPI and ODCS can be tracked and versioned but need explicit custom lint/diff commands or disabled checks. They have no built-in governance engines. Fixed versioning, AI features, and generation hooks are not implemented; do not advertise them as stable functionality. + +## Prepare a reviewed release + +1. Run all checks in [CONTRIBUTING.md](CONTRIBUTING.md). +2. Present Omer with the exact commit, proposed package versions, npm distribution tag, and validation results. Obtain approval before preparing version changes or running a release workflow. +3. Run **Prepare Release** from `next`. It opens a version PR targeting `next`; it does not create tags or publish. Review and merge that PR normally. +4. Obtain approval for the exact merged release commit, then run **Publish Packages** with that full commit SHA and the approved distribution tag. `latest` rejects prerelease versions. Approve the `release` environment job in GitHub. +5. Verify package installations and imports from npm. Update the Action's pinned dependencies, rebuild its committed bundle, and review the Action PR before requesting approval for an Action tag or GitHub Release. + +Tag creation is separate and manual. Do not move existing development tags or publish a stable major alias without approval. The old `next → master` PR is historical and is not a prerequisite for releasing from `next`. + +## Publishing failures + +The June 18 attempt left CLI `0.1.0-dev.8` and changesets `0.1.0-dev.6` tagged but unpublished. The last npm CLI release is `0.1.0-dev.7` under `dev`; `latest` still points to `0.1.0-dev.0`. Do not repair these registry tags without approval. + +Publishing preflight checks authentication, package write access, repository metadata, and version/tag compatibility before running Lerna. If npm returns `E404`, verify the configured `NPM_TOKEN` can write every `@contractual` package and that npm access and provenance match `codotech/contractual`. Never print a token in logs. A successful build is not evidence of npm authorization. diff --git a/e2e/cli-basic/package.json b/e2e/cli-basic/package.json index f907462..dfb5a3e 100644 --- a/e2e/cli-basic/package.json +++ b/e2e/cli-basic/package.json @@ -15,6 +15,6 @@ "test": "vitest run" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } } diff --git a/e2e/cli-lifecycle/package.json b/e2e/cli-lifecycle/package.json index 78d3385..96a432e 100644 --- a/e2e/cli-lifecycle/package.json +++ b/e2e/cli-lifecycle/package.json @@ -16,6 +16,6 @@ "test": "vitest run" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } } diff --git a/e2e/packages-import/package.json b/e2e/packages-import/package.json index ee0f6d8..34c3b20 100644 --- a/e2e/packages-import/package.json +++ b/e2e/packages-import/package.json @@ -18,6 +18,6 @@ "test": "tsc --noEmit && vitest run" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" } } diff --git a/package.json b/package.json index 14a20c1..c4cf13b 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,17 @@ "name": "contractual-monorepo", "private": true, "version": "0.0.0", - "license": "Apache-2.0", + "license": "MIT", "type": "module", "repository": { "type": "git", - "url": "https://github.com/contractual-dev/contractual.git" + "url": "https://github.com/codotech/contractual.git" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" }, "bugs": { - "url": "https://github.com/contractual-dev/contractual.git" + "url": "https://github.com/codotech/contractual.git" }, "homepage": "https://contractual.dev", "contributors": [ @@ -24,13 +24,12 @@ "scripts": { "build": "pnpm lerna run build --stream", "build:watch": "pnpm -r run build:watch", - "test": "pnpm lerna run test --stream", + "test": "vitest run", "test:e2e": "vitest run --config vitest.config.e2e.ts", "test:e2e:watch": "vitest --config vitest.config.e2e.ts", "lint": "pnpm lerna run lint --parallel", "prepare": "husky", "version:preview": "lerna changed --json | jq -r '.[] | \"\\(.name) → v\\(.version)\"'", - "publish:dry": "lerna publish from-package --yes --no-git-reset --dry-run", "e2e:verdaccio:up": "docker compose -f local-e2e.docker-compose.yaml up -d", "e2e:verdaccio:down": "docker compose -f local-e2e.docker-compose.yaml down -v" }, diff --git a/packages/changesets/package.json b/packages/changesets/package.json index 5ab172d..4e11066 100644 --- a/packages/changesets/package.json +++ b/packages/changesets/package.json @@ -25,11 +25,11 @@ "sideEffects": false, "repository": { "type": "git", - "url": "https://github.com/contractual-dev/contractual.git", + "url": "https://github.com/codotech/contractual.git", "directory": "packages/changesets" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" }, "scripts": { "prebuild": "pnpm rimraf dist", diff --git a/packages/cli/package.json b/packages/cli/package.json index f3fa679..f4dff6e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -17,12 +17,12 @@ }, "repository": { "type": "git", - "url": "https://github.com/contractual-dev/contractual.git", + "url": "https://github.com/codotech/contractual.git", "directory": "packages/cli" }, "homepage": "https://contractual.dev", "bugs": { - "url": "https://github.com/contractual-dev/contractual/issues" + "url": "https://github.com/codotech/contractual/issues" }, "contributors": [ { @@ -41,7 +41,7 @@ } ], "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" }, "scripts": { "prebuild": "pnpm rimraf dist", diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index cd3a3d6..ae73ec5 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -1,4 +1,5 @@ import { Command } from 'commander'; +import { readFileSync } from 'node:fs'; import { initCommand } from './commands/init.command.js'; import { contractAddCommand, contractListCommand } from './commands/contract.command.js'; import { lintCommand } from './commands/lint.command.js'; @@ -11,7 +12,12 @@ import { statusCommand } from './commands/status.command.js'; const program = new Command(); -program.name('contractual').description('Schema contract lifecycle orchestrator').version('0.1.0'); +const packageVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) + .version as string; +program + .name('contractual') + .description('Schema contract lifecycle orchestrator') + .version(packageVersion); program .command('init') diff --git a/packages/cli/src/commands/lint.command.ts b/packages/cli/src/commands/lint.command.ts index a2a7ca1..dda57d2 100644 --- a/packages/cli/src/commands/lint.command.ts +++ b/packages/cli/src/commands/lint.command.ts @@ -38,7 +38,7 @@ function getLinterForContract(contract: ResolvedContract): LinterLookupResult { const linter = getRegisteredLinter(contract.type, contract.lint); if (linter === null) { - return { status: 'disabled' }; + return { status: 'not-found', type: contract.type }; } if (!linter) { @@ -106,6 +106,18 @@ export async function lintCommand(options: LintOptions = {}): Promise