diff --git a/deploy/action.yaml b/deploy/action.yaml index 7d5306d..036dcd6 100644 --- a/deploy/action.yaml +++ b/deploy/action.yaml @@ -13,6 +13,13 @@ inputs: description: Working directory for the project (for monorepo setups) required: false default: "." + package-manager: + description: >- + Package manager used to run tailor-sdk (pnpm, npm, yarn, or bun). + Defaults to npx when empty, so Bun uses its own runtime instead of + relying on an ambient Node, and the locally-installed version is used. + required: false + default: "" platform-client-id: description: OAuth2 client ID for Tailor Platform machine user required: true @@ -37,13 +44,32 @@ runs: CLIENT_ID: ${{ inputs.platform-client-id }} CLIENT_SECRET: ${{ inputs.platform-client-secret }} + - name: Resolve tailor-sdk runner + shell: bash + env: + PACKAGE_MANAGER: ${{ inputs.package-manager }} + # RUN is one of a fixed set of literals chosen by the case below (never + # user input), so writing it to GITHUB_ENV cannot inject code. + run: | # zizmor: ignore[github-env] + case "$PACKAGE_MANAGER" in + pnpm) RUN="pnpm exec" ;; + yarn) RUN="yarn" ;; + bun) RUN="bunx" ;; + npm|"") RUN="npx" ;; + *) + echo "::error::Unsupported package-manager '$PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)" + exit 1 + ;; + esac + echo "TAILOR_SDK_RUN=$RUN" >> "$GITHUB_ENV" + - name: Login to Tailor Platform shell: bash working-directory: ${{ inputs.working-directory }} env: TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID: ${{ inputs.platform-client-id }} TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET: ${{ inputs.platform-client-secret }} - run: npx tailor-sdk login --machineuser + run: $TAILOR_SDK_RUN tailor-sdk login --machineuser - name: Validate workspace-id shell: bash @@ -60,11 +86,11 @@ runs: working-directory: ${{ inputs.working-directory }} env: TAILOR_PLATFORM_WORKSPACE_ID: ${{ inputs.workspace-id }} - run: npx tailor-sdk generate + run: $TAILOR_SDK_RUN tailor-sdk generate - name: Deploy shell: bash working-directory: ${{ inputs.working-directory }} env: TAILOR_PLATFORM_WORKSPACE_ID: ${{ inputs.workspace-id }} - run: npx tailor-sdk apply --yes + run: $TAILOR_SDK_RUN tailor-sdk apply --yes diff --git a/generate-check/action.yaml b/generate-check/action.yaml new file mode 100644 index 0000000..3293d03 --- /dev/null +++ b/generate-check/action.yaml @@ -0,0 +1,47 @@ +name: Check Tailor Platform generated files +description: >- + Run tailor-sdk generate and fail if it produces changes, catching generated + files (seed data, enum constants, etc.) that were not regenerated and + committed. Requires Node.js and dependencies to be installed by the caller. + +inputs: + package-manager: + description: Package manager used to run tailor-sdk (pnpm, npm, yarn, or bun) + required: true + working-directory: + description: Working directory for the project (for monorepo setups) + required: false + default: "." + +runs: + using: composite + steps: + - name: Generate + shell: bash + working-directory: ${{ inputs.working-directory }} + env: + PACKAGE_MANAGER: ${{ inputs.package-manager }} + run: | + # Run the locally-installed tailor-sdk with each package manager's own + # runner, so the pinned version is used and Bun does not depend on an + # ambient Node/npx being present on the runner. + case "$PACKAGE_MANAGER" in + pnpm) pnpm exec tailor-sdk generate ;; + npm) npx tailor-sdk generate ;; + yarn) yarn tailor-sdk generate ;; + bun) bunx tailor-sdk generate ;; + *) + echo "::error::Unsupported package-manager '$PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)" + exit 1 + ;; + esac + + - name: Check generated files are committed + shell: bash + run: | + CHANGES=$(git status --porcelain) + if [ -n "$CHANGES" ]; then + echo "$CHANGES" + echo "::error::Generated files are out of date. Run 'tailor-sdk generate' locally and commit the result." + exit 1 + fi diff --git a/plan/action.yaml b/plan/action.yaml index f6e58ae..4d00e4c 100644 --- a/plan/action.yaml +++ b/plan/action.yaml @@ -21,6 +21,13 @@ inputs: description: Working directory for the project (for monorepo setups) required: false default: "." + package-manager: + description: >- + Package manager used to run tailor-sdk (pnpm, npm, yarn, or bun). + Defaults to npx when empty, so Bun uses its own runtime instead of + relying on an ambient Node, and the locally-installed version is used. + required: false + default: "" platform-client-id: description: OAuth2 client ID for Tailor Platform machine user required: true @@ -53,13 +60,32 @@ runs: CLIENT_ID: ${{ inputs.platform-client-id }} CLIENT_SECRET: ${{ inputs.platform-client-secret }} + - name: Resolve tailor-sdk runner + shell: bash + env: + PACKAGE_MANAGER: ${{ inputs.package-manager }} + # RUN is one of a fixed set of literals chosen by the case below (never + # user input), so writing it to GITHUB_ENV cannot inject code. + run: | # zizmor: ignore[github-env] + case "$PACKAGE_MANAGER" in + pnpm) RUN="pnpm exec" ;; + yarn) RUN="yarn" ;; + bun) RUN="bunx" ;; + npm|"") RUN="npx" ;; + *) + echo "::error::Unsupported package-manager '$PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)" + exit 1 + ;; + esac + echo "TAILOR_SDK_RUN=$RUN" >> "$GITHUB_ENV" + - name: Login to Tailor Platform shell: bash working-directory: ${{ inputs.working-directory }} env: TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID: ${{ inputs.platform-client-id }} TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET: ${{ inputs.platform-client-secret }} - run: npx tailor-sdk login --machineuser + run: $TAILOR_SDK_RUN tailor-sdk login --machineuser - name: Merge base branch if: github.event_name == 'pull_request' @@ -79,7 +105,7 @@ runs: TAILOR_PLATFORM_WORKSPACE_ID: ${{ inputs.workspace-id }} run: | set +e - OUTPUT=$(npx tailor-sdk apply --dry-run --yes 2>&1) + OUTPUT=$($TAILOR_SDK_RUN tailor-sdk apply --dry-run --yes 2>&1) EXIT_CODE=$? set -e diff --git a/setup/action.yaml b/setup/action.yaml new file mode 100644 index 0000000..79edd6a --- /dev/null +++ b/setup/action.yaml @@ -0,0 +1,65 @@ +name: Set up Tailor Platform toolchain +description: >- + Set up Node.js (or Bun) and install project dependencies for the configured + package manager, so subsequent steps can run tailor-sdk. The caller is + responsible for checking out the repository first. + +inputs: + package-manager: + description: Package manager to use (pnpm, npm, yarn, or bun) + required: true + node-version-file: + description: File to read the Node.js version from + required: false + default: package.json + install-command: + description: >- + Override the dependency-install command (e.g. a filtered monorepo + install). When empty, a frozen-lockfile install for the selected + package manager is used. + required: false + default: "" + working-directory: + description: Working directory to install dependencies in (for monorepo setups) + required: false + default: "." + +runs: + using: composite + steps: + - name: Set up pnpm + if: inputs.package-manager == 'pnpm' + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + + - name: Set up Node.js + if: inputs.package-manager != 'bun' + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: ${{ inputs.node-version-file }} + cache: ${{ inputs.package-manager }} + + - name: Set up Bun + if: inputs.package-manager == 'bun' + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + + - name: Install dependencies + shell: bash + working-directory: ${{ inputs.working-directory }} + env: + PACKAGE_MANAGER: ${{ inputs.package-manager }} + INSTALL_COMMAND: ${{ inputs.install-command }} + run: | + if [ -n "$INSTALL_COMMAND" ]; then + eval "$INSTALL_COMMAND" + else + case "$PACKAGE_MANAGER" in + pnpm) pnpm install --frozen-lockfile ;; + npm) npm ci ;; + yarn) yarn install --frozen-lockfile ;; + bun) bun install --frozen-lockfile ;; + *) + echo "::error::Unsupported package-manager '$PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)" + exit 1 + ;; + esac + fi diff --git a/tag-guard/action.yaml b/tag-guard/action.yaml new file mode 100644 index 0000000..8f8fbef --- /dev/null +++ b/tag-guard/action.yaml @@ -0,0 +1,38 @@ +name: Guard tag reachability from a branch +description: >- + Determine whether the pushed tag's commit is reachable from a target branch, + so a tag-triggered deploy can be limited to tags cut from that branch. The + caller must check out the repository with full history (fetch-depth: 0). + +inputs: + target-branch: + description: Branch the tag must be reachable from (e.g. main) + required: true + +outputs: + on-branch: + description: "'true' when the tag commit is reachable from the target branch, otherwise 'false'" + value: ${{ steps.guard.outputs.on-branch }} + +runs: + using: composite + steps: + - name: Check tag reachability + id: guard + shell: bash + env: + TARGET_BRANCH: ${{ inputs.target-branch }} + run: | + # Accept both short names (main) and fully-qualified refs (refs/heads/main). + BRANCH="${TARGET_BRANCH#refs/heads/}" + git fetch origin "$BRANCH" + # GITHUB_SHA may be a tag object for annotated tags; peel to its commit + # so --is-ancestor (which expects commits) is reliable. + COMMIT="$(git rev-parse "${GITHUB_SHA}^{commit}")" + if git merge-base --is-ancestor "$COMMIT" "origin/$BRANCH"; then + echo "on-branch=true" >> "$GITHUB_OUTPUT" + else + # A tag outside the target branch is not an error — just skip. + echo "on-branch=false" >> "$GITHUB_OUTPUT" + echo "::notice::Tag $GITHUB_REF_NAME is not reachable from $BRANCH; skipping deploy." + fi