diff --git a/.github/workflows/pkg_artifacts.yml b/.github/workflows/pkg_artifacts.yml new file mode 100644 index 0000000000..24ddbf3725 --- /dev/null +++ b/.github/workflows/pkg_artifacts.yml @@ -0,0 +1,148 @@ +name: Package Artifacts + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + +jobs: + build: + name: Build Packages + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Parse .tool-versions + uses: wistia/parse-tool-versions@v2.1.1 + with: + filename: '.tool-versions' + uppercase: 'true' + prefix: 'tool_version_' + + - uses: pnpm/action-setup@v4 + with: + version: '${{ env.TOOL_VERSION_PNPM }}' + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '${{ env.TOOL_VERSION_NODEJS }}' + cache: pnpm + + - name: Configure pnpm + run: | + pnpm config set auto-install-peers true + pnpm config set exclude-links-from-lockfile true + + - name: Sanitize branch name + env: + BRANCH: ${{ github.head_ref }} + run: | + echo "BRANCH_ID=$(echo "$BRANCH" | sed 's/[^0-9A-Za-z-]/-/g')" >> "$GITHUB_ENV" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build JS SDK + working-directory: packages/js-sdk + run: pnpm run build + + - name: Pack JS SDK + working-directory: packages/js-sdk + run: | + npm version prerelease --preid=${{ env.BRANCH_ID }} --no-git-tag-version + npm pack + + - name: Upload JS SDK artifact + uses: actions/upload-artifact@v4 + with: + name: e2b-js-sdk + path: packages/js-sdk/*.tgz + + - name: Build CLI + working-directory: packages/cli + run: pnpm run build + + - name: Pack CLI + working-directory: packages/cli + run: | + npm version prerelease --preid=${{ env.BRANCH_ID }} --no-git-tag-version + npm pack + + - name: Upload CLI artifact + uses: actions/upload-artifact@v4 + with: + name: e2b-cli + path: packages/cli/*.tgz + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '${{ env.TOOL_VERSION_PYTHON }}' + + - name: Install and configure Poetry + uses: snok/install-poetry@v1 + with: + version: '${{ env.TOOL_VERSION_POETRY }}' + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - name: Build Python SDK + working-directory: packages/python-sdk + run: | + BASE_VERSION=$(poetry version -s) + poetry version "${BASE_VERSION}+${BRANCH_ID}" + poetry build + + - name: Upload Python SDK artifact + uses: actions/upload-artifact@v4 + with: + name: e2b-python-sdk + path: packages/python-sdk/dist/* + + - name: Comment on PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + JS_VERSION=$(node -p "require('./packages/js-sdk/package.json').version") + CLI_VERSION=$(node -p "require('./packages/cli/package.json').version") + JS_TGZ=$(ls packages/js-sdk/*.tgz | xargs -n1 basename) + CLI_TGZ=$(ls packages/cli/*.tgz | xargs -n1 basename) + PY_VERSION=$(grep '^version' packages/python-sdk/pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + PY_WHL=$(ls packages/python-sdk/dist/*.whl | xargs -n1 basename) + + BODY=""$'\n' + BODY+="### Package Artifacts"$'\n\n' + BODY+="Built from ${GITHUB_SHA::7}. Download artifacts from [this workflow run](${RUN_URL})."$'\n\n' + BODY+="**JS SDK** (\`e2b@${JS_VERSION}\`):"$'\n' + BODY+='```sh'$'\n' + BODY+="npm install ./${JS_TGZ}"$'\n' + BODY+='```'$'\n\n' + BODY+="**CLI** (\`@e2b/cli@${CLI_VERSION}\`):"$'\n' + BODY+='```sh'$'\n' + BODY+="npm install ./${CLI_TGZ}"$'\n' + BODY+='```'$'\n\n' + BODY+="**Python SDK** (\`e2b==${PY_VERSION}\`):"$'\n' + BODY+='```sh'$'\n' + BODY+="pip install ./${PY_WHL}"$'\n' + BODY+='```'$'\n' + + COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ + --paginate \ + --jq '.[] | select(.body | contains("")) | .id' \ + | tail -1) + + if [ -n "$COMMENT_ID" ]; then + gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \ + -X PATCH -f body="$BODY" + else + gh pr comment "$PR_NUMBER" --body "$BODY" + fi diff --git a/.github/workflows/publish_candidates.yml b/.github/workflows/publish_candidates.yml new file mode 100644 index 0000000000..dc6425379d --- /dev/null +++ b/.github/workflows/publish_candidates.yml @@ -0,0 +1,115 @@ +name: Publish Candidates + +on: + workflow_call: + inputs: + js-sdk: + required: false + type: boolean + default: false + python-sdk: + required: false + type: boolean + default: false + cli: + required: false + type: boolean + default: false + tag: + required: true + type: string + preid: + required: true + type: string + +permissions: + contents: read + id-token: write + +jobs: + publish: + name: Publish Release Candidates + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Parse .tool-versions + uses: wistia/parse-tool-versions@v2.1.1 + with: + filename: '.tool-versions' + uppercase: 'true' + prefix: 'tool_version_' + + - uses: pnpm/action-setup@v4 + if: ${{ inputs.js-sdk || inputs.cli }} + with: + version: '${{ env.TOOL_VERSION_PNPM }}' + + - name: Setup Node.js + uses: actions/setup-node@v6 + if: ${{ inputs.js-sdk || inputs.cli }} + with: + node-version: '${{ env.TOOL_VERSION_NODEJS }}' + registry-url: https://registry.npmjs.org + cache: pnpm + + - name: Configure pnpm + if: ${{ inputs.js-sdk || inputs.cli }} + run: | + pnpm config set auto-install-peers true + pnpm config set exclude-links-from-lockfile true + + - name: Update npm + if: ${{ inputs.js-sdk || inputs.cli }} + run: | + npm install -g npm@^11.6 + npm --version + + - name: Set up Python + uses: actions/setup-python@v4 + if: ${{ inputs.python-sdk }} + with: + python-version: '${{ env.TOOL_VERSION_PYTHON }}' + + - name: Install and configure Poetry + uses: snok/install-poetry@v1 + if: ${{ inputs.python-sdk }} + with: + version: '${{ env.TOOL_VERSION_POETRY }}' + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - name: Publish Python RC + if: ${{ inputs.python-sdk }} + working-directory: packages/python-sdk + run: | + BASE_VERSION=$(poetry version -s) + poetry version "${BASE_VERSION}rc${{ github.run_number }}" + poetry build + poetry config pypi-token.pypi ${PYPI_TOKEN} && poetry publish --skip-existing + env: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + + - name: Install JS dependencies + if: ${{ inputs.js-sdk || inputs.cli }} + run: pnpm install --frozen-lockfile + + - name: Publish JS RC + if: ${{ inputs.js-sdk }} + working-directory: packages/js-sdk + run: | + npm version prerelease --preid=${{ inputs.preid }}.${{ github.run_number }} --no-git-tag-version + npm publish --tag ${{ inputs.tag }} --provenance + + - name: Reinstall dependencies + if: ${{ inputs.js-sdk || inputs.cli }} + run: pnpm install --frozen-lockfile + + - name: Publish CLI RC + if: ${{ inputs.cli }} + working-directory: packages/cli + run: | + npm version prerelease --preid=${{ inputs.preid }}.${{ github.run_number }} --no-git-tag-version + npm publish --tag ${{ inputs.tag }} --provenance diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fff4f1d761..ad5cc31d22 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,44 @@ name: Release on: workflow_dispatch: + inputs: + mode: + description: 'Release mode' + type: choice + options: + - release + - candidate + default: release + js-sdk: + description: 'Release JS SDK (candidate only)' + required: false + default: false + type: boolean + python-sdk: + description: 'Release Python SDK (candidate only)' + required: false + default: false + type: boolean + cli: + description: 'Release CLI (candidate only)' + required: false + default: false + type: boolean + tag: + description: 'Dist-tag for candidate (e.g. rc, beta, snapshot)' + required: false + default: 'rc' + type: string + preid: + description: 'Prerelease identifier (defaults to branch name, candidate only)' + required: false + default: '' + type: string + skip-tests: + description: 'Skip tests (candidate only)' + required: false + default: false + type: boolean concurrency: ${{ github.workflow }}-${{ github.ref }} @@ -10,11 +48,16 @@ permissions: contents: write jobs: - is_release: - name: Is release? + # ── Production release ─────────────────────────────────── + preflight: + name: Release preflight + if: github.event.inputs.mode != 'candidate' runs-on: ubuntu-latest outputs: release: ${{ steps.version.outputs.release }} + js-sdk: ${{ steps.js.outputs.release }} + python-sdk: ${{ steps.python.outputs.release }} + cli: ${{ steps.cli.outputs.release }} steps: - name: Checkout Repo uses: actions/checkout@v3 @@ -54,91 +97,52 @@ jobs: IS_RELEASE=$(./.github/scripts/is_release.sh) echo "release=$IS_RELEASE" >> "$GITHUB_OUTPUT" - changes: - name: Repository changes - needs: [is_release] - if: needs.is_release.outputs.release == 'true' - runs-on: ubuntu-latest - outputs: - js-sdk: ${{ steps.js.outputs.release }} - python-sdk: ${{ steps.python.outputs.release }} - cli: ${{ steps.cli.outputs.release }} - steps: - - name: Checkout Repo - uses: actions/checkout@v3 - - - name: Parse .tool-versions - uses: wistia/parse-tool-versions@v2.1.1 - with: - filename: '.tool-versions' - uppercase: 'true' - prefix: 'tool_version_' - - - name: Install pnpm - uses: pnpm/action-setup@v4 - id: pnpm-install - with: - version: '${{ env.TOOL_VERSION_PNPM }}' - - - name: Setup Node - uses: actions/setup-node@v6 - with: - node-version: '${{ env.TOOL_VERSION_NODEJS }}' - registry-url: 'https://registry.npmjs.org' - cache: pnpm - cache-dependency-path: pnpm-lock.yaml - - - name: Configure pnpm - run: | - pnpm config set auto-install-peers true - pnpm config set exclude-links-from-lockfile true - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Check JavasScript SDK Release + - name: Check JavaScript SDK Release id: js + if: steps.version.outputs.release == 'true' run: | IS_RELEASE=$(./.github/scripts/is_release_for_package.sh "e2b") echo "release=$IS_RELEASE" >> "$GITHUB_OUTPUT" - name: Check Python SDK Release id: python + if: steps.version.outputs.release == 'true' run: | IS_RELEASE=$(./.github/scripts/is_release_for_package.sh "@e2b/python-sdk") echo "release=$IS_RELEASE" >> "$GITHUB_OUTPUT" - name: Check CLI Release id: cli + if: steps.version.outputs.release == 'true' run: | IS_RELEASE=$(./.github/scripts/is_release_for_package.sh "@e2b/cli") echo "release=$IS_RELEASE" >> "$GITHUB_OUTPUT" python-tests: name: Python SDK Tests - needs: [changes] - if: needs.changes.outputs.python-sdk == 'true' + needs: [preflight] + if: needs.preflight.outputs.python-sdk == 'true' uses: ./.github/workflows/python_sdk_tests.yml secrets: inherit js-tests: name: JS SDK Tests - needs: [changes] - if: needs.changes.outputs.js-sdk == 'true' + needs: [preflight] + if: needs.preflight.outputs.js-sdk == 'true' uses: ./.github/workflows/js_sdk_tests.yml secrets: inherit cli-tests: name: CLI Tests - needs: [changes] - if: needs.changes.outputs.cli == 'true' + needs: [preflight] + if: needs.preflight.outputs.cli == 'true' uses: ./.github/workflows/cli_tests.yml secrets: inherit publish: name: Publish - needs: [is_release, python-tests, js-tests, cli-tests] - if: (!cancelled()) && !contains(needs.*.result, 'failure') && needs.is_release.outputs.release == 'true' + needs: [preflight, python-tests, js-tests, cli-tests] + if: (!cancelled()) && !contains(needs.*.result, 'failure') && needs.preflight.outputs.release == 'true' uses: ./.github/workflows/publish_packages.yml secrets: inherit @@ -156,3 +160,66 @@ jobs: SLACK_TITLE: Release Failed SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} SLACK_CHANNEL: 'monitoring-releases' + + # ── Release candidate ──────────────────────────────────── + rc-validate: + name: Validate RC inputs + if: github.event.inputs.mode == 'candidate' + runs-on: ubuntu-latest + outputs: + preid: ${{ steps.preid.outputs.preid }} + tag: ${{ steps.tag.outputs.tag }} + steps: + - name: Sanitize tag + id: tag + run: | + RAW_TAG="${{ github.event.inputs.tag }}" + SAFE_TAG="$(echo "$RAW_TAG" | sed 's/[^0-9A-Za-z-]/-/g')" + echo "tag=$SAFE_TAG" >> "$GITHUB_OUTPUT" + + - name: Block production tags + run: | + if [ "${{ steps.tag.outputs.tag }}" = "latest" ]; then + echo "::error::Publishing with the 'latest' tag is not allowed for candidates. Use 'release' mode instead." + exit 1 + fi + + - name: Sanitize preid + id: preid + run: | + RAW_PREID="${{ github.event.inputs.preid || github.ref_name }}" + echo "preid=$(echo "$RAW_PREID" | sed 's/[^0-9A-Za-z-]/-/g')" >> "$GITHUB_OUTPUT" + + rc-python-tests: + name: RC Python Tests + needs: [rc-validate] + if: github.event.inputs.python-sdk == 'true' && github.event.inputs.skip-tests != 'true' + uses: ./.github/workflows/python_sdk_tests.yml + secrets: inherit + + rc-js-tests: + name: RC JS Tests + needs: [rc-validate] + if: github.event.inputs.js-sdk == 'true' && github.event.inputs.skip-tests != 'true' + uses: ./.github/workflows/js_sdk_tests.yml + secrets: inherit + + rc-cli-tests: + name: RC CLI Tests + needs: [rc-validate] + if: github.event.inputs.cli == 'true' && github.event.inputs.skip-tests != 'true' + uses: ./.github/workflows/cli_tests.yml + secrets: inherit + + rc-publish: + name: Publish RC + needs: [rc-validate, rc-python-tests, rc-js-tests, rc-cli-tests] + if: (!cancelled()) && !contains(needs.*.result, 'failure') && needs.rc-validate.result == 'success' + uses: ./.github/workflows/publish_candidates.yml + with: + js-sdk: ${{ github.event.inputs.js-sdk == 'true' }} + python-sdk: ${{ github.event.inputs.python-sdk == 'true' }} + cli: ${{ github.event.inputs.cli == 'true' }} + tag: ${{ needs.rc-validate.outputs.tag }} + preid: ${{ needs.rc-validate.outputs.preid }} + secrets: inherit diff --git a/.github/workflows/release_candidates.yml b/.github/workflows/release_candidates.yml deleted file mode 100644 index 0e7cad52f1..0000000000 --- a/.github/workflows/release_candidates.yml +++ /dev/null @@ -1,174 +0,0 @@ -name: Release Candidates - -on: - pull_request: - types: - - labeled - - opened - - reopened - - synchronize - workflow_dispatch: - inputs: - js-sdk: - description: 'Release JS SDK' - required: false - default: false - type: boolean - python-sdk: - description: 'Release Python SDK' - required: false - default: false - type: boolean - cli: - description: 'Release CLI' - required: false - default: false - type: boolean - tag: - description: 'Dist-tag (e.g. rc, beta, snapshot)' - required: true - type: string - preid: - description: 'Prerelease identifier (defaults to branch name if empty)' - required: false - default: '' - type: string - -permissions: - contents: write - -jobs: - release: - name: Release Candidate - runs-on: ubuntu-latest - - env: - IS_JS: ${{ contains(github.event.pull_request.labels.*.name, 'js-rc') || (github.event_name == 'workflow_dispatch' && github.event.inputs.js-sdk == 'true') }} - IS_CLI: ${{ contains(github.event.pull_request.labels.*.name, 'cli-rc') || (github.event_name == 'workflow_dispatch' && github.event.inputs.cli == 'true') }} - IS_PYTHON: ${{ contains(github.event.pull_request.labels.*.name, 'python-rc') || (github.event_name == 'workflow_dispatch' && github.event.inputs.python-sdk == 'true') }} - PUBLISH_TAG: ${{ github.event.inputs.tag || 'rc' }} - - steps: - - name: Sanitize preid - run: | - RAW_PREID="${{ github.event.inputs.preid || github.head_ref || github.ref_name }}" - echo "PREID=$(echo "$RAW_PREID" | sed 's/[^0-9A-Za-z-]/-/g')" >> "$GITHUB_ENV" - - - name: Block production tags - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - if [ "${{ github.event.inputs.tag }}" = "latest" ]; then - echo "::error::Publishing with the 'latest' tag is not allowed from this workflow. Use the Release workflow instead." - exit 1 - fi - - - name: Checkout Repo - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref || github.ref }} - - - name: Parse .tool-versions - uses: wistia/parse-tool-versions@v2.1.1 - with: - filename: '.tool-versions' - uppercase: 'true' - prefix: 'tool_version_' - - - uses: pnpm/action-setup@v4 - if: ${{ env.IS_JS == 'true' || env.IS_CLI == 'true' }} - with: - version: '${{ env.TOOL_VERSION_PNPM }}' - - - name: Setup Node.js - uses: actions/setup-node@v4 - if: ${{ env.IS_JS == 'true' || env.IS_CLI == 'true' || env.IS_PYTHON == 'true' }} - with: - node-version: '${{ env.TOOL_VERSION_NODEJS }}' - registry-url: https://registry.npmjs.org - cache: pnpm - - - name: Configure pnpm - if: ${{ env.IS_JS == 'true' || env.IS_CLI == 'true' }} - run: | - pnpm config set auto-install-peers true - pnpm config set exclude-links-from-lockfile true - - - name: Set up Python - uses: actions/setup-python@v4 - if: ${{ env.IS_PYTHON == 'true' }} - with: - python-version: '${{ env.TOOL_VERSION_PYTHON }}' - - - name: Install and configure Poetry - uses: snok/install-poetry@v1 - if: ${{ env.IS_PYTHON == 'true' }} - with: - version: '${{ env.TOOL_VERSION_POETRY }}' - virtualenvs-create: true - virtualenvs-in-project: true - installer-parallel: true - - - name: Test Python SDK - if: ${{ env.IS_PYTHON == 'true' }} - working-directory: packages/python-sdk - run: | - poetry install - poetry run pytest --verbose -x - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} - - - name: Release Candidate - if: ${{ env.IS_PYTHON == 'true' }} - working-directory: packages/python-sdk - run: | - poetry version prerelease - poetry build - poetry config pypi-token.pypi ${PYPI_TOKEN} && poetry publish --skip-existing - env: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - - - name: Install JS dependencies - if: ${{ env.IS_JS == 'true' || env.IS_CLI == 'true' }} - run: | - pnpm install --frozen-lockfile - - - name: Test JS SDK - working-directory: packages/js-sdk - if: ${{ env.IS_JS == 'true' }} - run: | - pnpm run test - env: - E2B_API_KEY: ${{ secrets.E2B_API_KEY }} - - - name: Release JS Candidate - working-directory: packages/js-sdk - if: ${{ env.IS_JS == 'true' }} - run: | - npm version prerelease --preid=${{ env.PREID }} - npm publish --tag ${{ env.PUBLISH_TAG }} - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Install dependencies - if: ${{ env.IS_JS == 'true' || env.IS_CLI == 'true' }} - run: | - pnpm install --frozen-lockfile - - - name: Release CLI Candidate - working-directory: packages/cli - if: ${{ env.IS_CLI == 'true' }} - run: | - npm version prerelease --preid=${{ env.PREID }} - npm publish --tag ${{ env.PUBLISH_TAG }} - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Commit new versions - if: ${{ env.IS_JS == 'true' || env.IS_CLI == 'true' || env.IS_PYTHON == 'true' }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git commit -am "[skip ci] Release new versions" || exit 0 - git push - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}