💡 機能画像は、OpenAI DALL-Eによって部分的に描かれた典型的なCI/CDパイプラインを示していますが、この記事では有益なものを開発します

これは、単一のmonorepoからGitHub Actions、Azure Pipelines、CircleCI用のCI拡張機能を開発、テスト、デプロイする方法についての比較的短いチュートリアルで、Qodana CI拡張機能の作成経験に基づいています。

公式テンプレートから始める

CI拡張機能の技術スタックを選択しましょう。

OK、選択しません。拡張機能にTypeScriptとnode.jsを使用した理由を説明します。

JSベースのアクションを使用する利点

  • bash/Dockerfileベースのアプローチよりも柔軟
  • テストの作成が比較的簡単

欠点

  • JavaScript

では、TypeScriptベースのアクションを書きましょう!

GitHub Actions

GitHub actionsのドキュメントはAzureよりも読みやすいと思いますので、公式テンプレートactions/typescript-actionを使用して、GitHub上で拡張機能の作成とテストを開始することをお勧めします。前述のテンプレートは良い出発点を提供します。ここでは手順を繰り返しません。使ってみて、簡単なものを書いて、次のステップのためにここに戻ってきてください。

Azure Pipelines

GitHub ActionsはAzureインフラストラクチャ上に構築されているため、GitHub actionをAzure Pipelinesに移植することは比較的簡単です。

つまり、

  • “action”が”task”になる
  • パッケージ化、配布、インストールの方法が少し異なる

そして、タスクの定義task.jsonはアクションの定義action.ymlと同じです。

例えば、次のaction.ymlがあるとします:

name: 'Your name here'
description: 'Provide a description here'
author: 'Your name or organization here'
inputs:
  milliseconds: # change this
    required: true
    description: 'input description here'
    default: 'default value if applicable'
runs:
  using: 'node16'
  main: 'dist/index.js'

これは次のAzureタスクに「簡単に」変換されます:

{
  "$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json",
  "id": "822d6cb9-d4d1-431b-9513-e7db7d718a49",
  "name": "YourTaskNameHere",
  "friendlyName": "Your name here",
  "description": "Provide a description here",
  "helpMarkDown": "Provide a longer description here",
  "author": "Your name or organization here",
  "version": {
    "Major": 1,
    "Minor": 0,
    "Patch": 0
  },
  "instanceNameFormat": "YourTaskNameHere",
  "inputs": [
    {
      "name": "milliseconds",
      "type": "string",
      "label": "label name here",
      "defaultValue": "default value if applicable",
      "required": true,
      "helpMarkDown": "input description here"
    }
  ],
  "execution": {
    "Node10": {
      "target": "index.js"
    }
  }
}

このような簡単な例から、GitHub Actionsから始めることを提案した理由がわかります。しかし、続けましょう。

新しいAzure Pipelinesタスクの開発を始めるには、actionディレクトリをコピーしてから、公式Azureドキュメントの手順を実装することをお勧めします。非常に簡単です。

  1. vss-extension.jsonを作成
  2. task.jsonを作成し、distディレクトリに配置(実際にはタスク名にちなんで命名する方が良い)
  3. actionで@actions/coreまたは@actions/githubのメソッドを使用した場合、azure-pipelines-task-libの対応するメソッドに置き換える必要があります(例:core.getInput tl.getInput

azure-pipelines-task-libのAPIは、@actions/coreや他の@actions/*ライブラリに似ています。 例えば、入力パラメータを取得するメソッドがあります:

export function getInputs(): Inputs {
  return {
    milliseconds: core.getInput('milliseconds'),
  }
}

Azure Pipelinesでも同じです:

export function getInputs(): Inputs {
  return {
    milliseconds: tl.getInput('milliseconds'),
  }
}

より実際的なケースについては、Qodana GitHub ActionsのコードベースutilsとAzure Pipelinesタスクのutilsを自由に調べてください。

monorepoを作成する

monorepoを管理するためにnpm workspacesを使用します。 新しく作成したmonorepoのサブディレクトリ(例:github)にactionとtaskのコードを配置します。次に、ルートディレクトリにpackage.jsonファイルを作成します。

{
  "name": "@org/ci",
  "version": "1.0.0",
  "description": "Common code for CI extensions",
  "license": "Apache-2.0",
  "workspaces": [
    "github",
    "azure"
  ],
  "devDependencies": {
    "typescript": "latest",
    "eslint": "latest",
    "eslint-plugin-github": "latest",
    "eslint-plugin-jest": "latest",
    "prettier": "latest",
    "ts-node": "latest"
  }
}
 

monorepo構造は次のようになります:

...
├── action.yaml
├── github/
├── azure/
└── package.json

workspaceの設定を実装した後、ルートディレクトリからタスクとアクションを実行できます。例えば、githubディレクトリからbuildタスクを実行するには、次のコマンドを使用できます:

npm run -w github build

アクションとタスク間でコードを共有する

monorepoアプローチを使用することの最も価値のある部分はここから始まります:アクションとタスク間でコードを共有できます。

次の手順を実行します:

  1. monorepoのルートにcommonディレクトリを作成し、共有コードのサブプロジェクトにする
  2. すべてのサブディレクトリからtsconfig.jsonコンパイラ設定を更新して、適切なプロジェクトビルドを行う

まず、すべてのサブプロジェクトで使用される基本設定を含む基本tsconfig - tsconfig.base.jsonを作成しましょう:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "composite": true
  },
  "exclude": ["node_modules", "**/*.test.ts", "*/lib/**"]
}

次に、プロジェクトルートに簡単なtsconfig.jsonを作成します:

{
  "references": [
    { "path": "common" },
    { "path": "azure" },
    { "path": "github" }
  ],
  "files": []
}

次にcommon/tsconfig.json

{
  "extends": "../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./lib",
    "rootDir": "."
  },
  "files": ["include your files here or use typical include/exclude patterns"]
}

最後に、サブプロジェクトのtsconfig.jsonファイルを更新します(基本的にすべて同じです。例:github/tsconfig.json):

{
  "extends": "../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./lib",
    "rootDir": "./src"
  },
  "references": [
    { "path": "../common" }
  ]
}

これで、アクションとタスクでcommonディレクトリから共有コードを使用できます。例えば、commonディレクトリにqodana.tsファイルがあり、Qodana CLIツールへのURLを返す関数getQodanaUrlが含まれています。そして、アクションとタスクの両方でそれを使用しています。

CleanShot 2023-06-18 at 16 54 11@2x

ビルドと公開

テンプレートから構成されたGitHub workflowsは、アクションをリポジトリリリースに公開するように既に設定されています。 自動リリースには、GH CLIを使用しており、リポジトリリリースに変更履歴を公開する簡単なスクリプトがあります:

#!/usr/bin/env bash
previous_tag=0
for current_tag in $(git tag --sort=-creatordate)
do
 
if [ "$previous_tag" != 0 ];then
    printf "## Changelog\n"
    git log ${current_tag}...${previous_tag} --pretty=format:'* %h %s' --reverse | grep -v Merge
    printf "\n"
    break
fi
previous_tag=${current_tag}
done

そして、それを実行するGitHub workflow:

name: 'Release'
on:
  push:
    tags:
      - '*'
permissions:
  contents: write
 
jobs:
  github:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - run: |
          ./changelog.sh > changelog.md
          gh release create ${GITHUB_REF##*/} -F changelog.md
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Azure Pipelinesタスクのリリースには、Azureの公式アプローチを使用できますが、GitHub actionsインフラストラクチャでも同じことができます。パブリッシャーツールはどこにでもインストールできるからです。したがって、私たちの場合は、簡単なGitHub workflowジョブで解決されます:

  azure:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set Node.js 12.x
        uses: actions/[email protected]
        with:
          node-version: 12.x
      - name: Install dependencies
        run: npm ci && cd vsts/QodanaScan && npm ci && npm i -g tfx-cli
      - name: Package and publish
        run: |
          cd vsts && npm run azure
          mv JetBrains.qodana-*.vsix qodana.vsix
          tfx extension publish --publisher JetBrains --vsix qodana.vsix -t $AZURE_TOKEN
        env:
          AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }}

この設定により、各タグプッシュ時に自動的にリリースが行われます。

git tag -a v1.0.0 -m "v1.0.0" && git push origin v1.0.0
CleanShot 2023-06-18 at 16 55 34@2x

CircleCI?

ああ、そうです、この記事ではCircleCI orbについても言及しました… CircleCIの設定は簡単ですが、TypeScript拡張機能をサポートしていないため、コードをDockerイメージまたはバイナリにパックして実行する必要があります。この投稿に含まれている唯一の理由は、monorepoアプローチでorbをビルドしており、それがうまく機能しているためです。

公式orbテンプレートを実装し、monorepoに配置します。構造は次のようになります:

...
├── action.yaml
├── github/
├── azure/
├── src/            # orb source code here
└── package.json

また、CircleCIがorbをリント、テスト、公開できるように、.circleci/ディレクトリをリポジトリにコミットすることを忘れないでください。

CleanShot 2023-06-18 at 16 49 57@2x