はじめに
SPFx(SharePoint Framework)でWebパーツを開発していると、毎回こんな作業が発生しませんか?
gulp bundle --ship でビルドgulp package-solution --ship でパッケージ化- SharePoint 管理センターを開いてアプリカタログにアップロード
- 「信頼して展開」をクリック
- 対象サイトを開いてアプリをインストール
- ページを開いてWebパーツを追加して動作確認
この手順をすべて Claude Code のカスタムコマンド一発 で自動化します。
全体アーキテクチャ
flowchart TD
A[開発者: /deploy-spfx 実行] --> B[Phase 1: ビルド & 検証]
B --> B1[npm ci]
B --> B2[gulp bundle --ship]
B --> B3[Jest ユニットテスト]
B --> B4[ESLint 静的解析]
B1 & B2 & B3 & B4 --> C[Phase 2: パッケージング]
C --> C1[gulp package-solution --ship]
C1 --> D[Phase 3: SPO デプロイ]
D --> D1[m365 login 認証]
D1 --> D2[App Catalog へアップロード]
D2 --> D3[アプリ展開 deploy]
D3 --> D4[サイトコレクションへインストール]
D4 --> E[Phase 4: 動作確認]
E --> E1[Playwright ブラウザ起動]
E1 --> E2[SharePoint ページナビゲート]
E2 --> E3[Webパーツ描画検証]
E3 --> E4[スモークテスト実行]
E4 --> F{結果}
F -->|成功| G[✅ デプロイ完了通知]
F -->|失敗| H[❌ ロールバック & エラー通知]
使用ツール
| ツール | 役割 |
|---|
| CLI for Microsoft 365 (m365) | GUI不要でSPOを操作するCLI |
| GitHub Actions | CI/CDパイプライン |
| Claude Code カスタムコマンド | /.claude/commands/ によるワンコマンド実行 |
| Playwright | SPO上のブラウザ動作確認 |
| Azure AD アプリ登録 | 非インタラクティブ認証 |
Step 1: Azure AD アプリ登録(非インタラクティブ認証の準備)
GUIなしでSPOにデプロイするには、証明書ベースの認証が必要です。
1-1. 証明書の生成
1
2
3
4
5
6
7
8
9
| # 自己署名証明書を生成
openssl req -x509 -newkey rsa:2048 -keyout spfx-deploy-key.pem \
-out spfx-deploy-cert.pem -days 365 -nodes \
-subj "/CN=spfx-deploy"
# pfx形式に変換(Azureポータルへのアップロード用)
openssl pkcs12 -export -out spfx-deploy.pfx \
-inkey spfx-deploy-key.pem -in spfx-deploy-cert.pem \
-passout pass:""
|
1-2. Azure AD アプリ登録
- Azure Portal → Azure Active Directory → アプリの登録 → 新規登録
- 名前:
spfx-deploy-automation、サポートされるアカウントの種類: この組織のディレクトリのみ - 登録後、証明書とシークレット → 証明書のアップロード →
spfx-deploy-cert.pem をアップロード
1-3. API アクセス許可の追加
| API | アクセス許可の種類 | 権限 | 用途 |
|---|
| SharePoint | アプリケーション | Sites.FullControl.All | アプリカタログ操作 |
| SharePoint | アプリケーション | AppCatalog.ReadWrite.All | アプリ展開 |
重要: 追加後、管理者の同意を与える をクリックしてください。
1-4. 必要な情報をメモ
- テナントID(ディレクトリID)
- クライアントID(アプリケーションID)
- 証明書のサムプリント
Step 2: GitHub Secrets の設定
リポジトリの Settings → Secrets and variables → Actions で以下を登録します。
AZURE_TENANT_ID = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_ID = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CERT_THUMBPRINT = ABC123...(証明書のサムプリント)
AZURE_CERT_PFX_B64 = $(base64 -w 0 spfx-deploy.pfx)
SPO_APP_CATALOG_URL = https://tenant.sharepoint.com/sites/appcatalog
SPO_TARGET_SITE_URL = https://tenant.sharepoint.com/sites/test
Step 3: GitHub Actions ワークフロー
.github/workflows/spfx-deploy.yml を作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
| name: SPFx Deploy to SharePoint Online
on:
push:
branches: ["main"]
paths: ["src/**", "sharepoint/**", "package.json"]
workflow_dispatch:
inputs:
target_site:
description: "デプロイ先サイトURL"
required: false
default: ""
jobs:
build-and-test:
runs-on: ubuntu-latest
outputs:
package-name: ${{ steps.package-info.outputs.name }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Unit tests
run: npm test -- --ci --coverage
- name: Bundle (production)
run: npx gulp bundle --ship
- name: Package solution
run: npx gulp package-solution --ship
- name: Get package name
id: package-info
run: |
PKG=$(ls sharepoint/solution/*.sppkg | head -1 | xargs basename)
echo "name=$PKG" >> $GITHUB_OUTPUT
- name: Upload .sppkg artifact
uses: actions/upload-artifact@v4
with:
name: spfx-package
path: sharepoint/solution/*.sppkg
deploy-to-spo:
needs: build-and-test
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Download .sppkg artifact
uses: actions/download-artifact@v4
with:
name: spfx-package
path: ./package
- name: Install CLI for Microsoft 365
run: npm install -g @pnp/cli-microsoft365
- name: Setup certificate
run: |
echo "${{ secrets.AZURE_CERT_PFX_B64 }}" | base64 -d > /tmp/deploy.pfx
- name: Login to Microsoft 365
run: |
m365 login \
--authType certificate \
--certificateFile /tmp/deploy.pfx \
--appId "${{ secrets.AZURE_CLIENT_ID }}" \
--tenant "${{ secrets.AZURE_TENANT_ID }}"
- name: Upload to App Catalog
id: upload-app
run: |
PKG_PATH=$(ls ./package/*.sppkg | head -1)
APP_ID=$(m365 spo app add \
--filePath "$PKG_PATH" \
--appCatalogUrl "${{ secrets.SPO_APP_CATALOG_URL }}" \
--overwrite \
--output json | jq -r '.UniqueId')
echo "app-id=$APP_ID" >> $GITHUB_OUTPUT
echo "Uploaded app ID: $APP_ID"
- name: Deploy app (tenant-wide)
run: |
m365 spo app deploy \
--id "${{ steps.upload-app.outputs.app-id }}" \
--appCatalogUrl "${{ secrets.SPO_APP_CATALOG_URL }}"
- name: Install app to target site
run: |
TARGET="${{ github.event.inputs.target_site || secrets.SPO_TARGET_SITE_URL }}"
m365 spo app install \
--id "${{ steps.upload-app.outputs.app-id }}" \
--siteUrl "$TARGET"
- name: Cleanup certificate
if: always()
run: rm -f /tmp/deploy.pfx
functional-verify:
needs: deploy-to-spo
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
cache: "npm"
- name: Install test dependencies
run: |
npm ci
npx playwright install chromium --with-deps
- name: Run Playwright smoke tests
env:
SPO_SITE_URL: ${{ secrets.SPO_TARGET_SITE_URL }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CERT_PFX_B64: ${{ secrets.AZURE_CERT_PFX_B64 }}
run: npx playwright test --project=chromium e2e/spfx-smoke.spec.ts
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-results
path: |
playwright-report/
test-results/
|
Step 4: Playwright でSPO動作確認
認証設定(MSAL対応)
e2e/auth.setup.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| import { chromium, FullConfig } from "@playwright/test";
import * as msal from "@azure/msal-node";
import * as fs from "fs";
async function globalSetup(config: FullConfig) {
const pfxBuffer = Buffer.from(process.env.AZURE_CERT_PFX_B64!, "base64");
const pca = new msal.ConfidentialClientApplication({
auth: {
clientId: process.env.AZURE_CLIENT_ID!,
authority: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}`,
clientCertificate: {
thumbprintSha256: process.env.AZURE_CERT_THUMBPRINT!,
privateKey: pfxBuffer.toString(),
},
},
});
const result = await pca.acquireTokenByClientCredential({
scopes: [`${process.env.SPO_SITE_URL}/.default`],
});
// アクセストークンをファイルに保存
fs.writeFileSync("/tmp/spo-token.json", JSON.stringify(result));
}
export default globalSetup;
|
スモークテスト
e2e/spfx-smoke.spec.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| import { test, expect } from "@playwright/test";
const SPO_SITE = process.env.SPO_SITE_URL!;
const TEST_PAGE = `${SPO_SITE}/SitePages/webpart-test.aspx`;
test.describe("SPFx Webパーツ動作確認", () => {
test("Webパーツが正常にレンダリングされる", async ({ page }) => {
// SharePointページへナビゲート
await page.goto(TEST_PAGE);
// Webパーツのコンテナが表示されるまで待機
await expect(
page.locator('[data-sp-web-part-id]').first()
).toBeVisible({ timeout: 30000 });
// Webパーツ固有の要素を確認(コンポーネント名に合わせて変更)
await expect(
page.locator(".your-webpart-root-class")
).toBeVisible();
// エラーが発生していないことを確認
const errorBoundary = page.locator('[class*="errorBoundary"]');
await expect(errorBoundary).not.toBeVisible();
});
test("Webパーツのプロパティが機能する", async ({ page }) => {
await page.goto(TEST_PAGE);
// 具体的な機能テスト(プロジェクトに合わせてカスタマイズ)
const webpart = page.locator("[data-sp-web-part-id]").first();
await expect(webpart).not.toContainText("Error");
// APIコールが成功しているか確認
const responses = await page.waitForResponse(
(resp) => resp.url().includes("/_api/") && resp.status() < 400
);
expect(responses).toBeTruthy();
});
});
|
Step 5: Claude Code カスタムコマンド
.claude/commands/deploy-spfx.md を作成すると、/deploy-spfx で一発実行できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| SPFxプロジェクトの検証・デプロイ・動作確認を実行してください。
## 実行手順
1. **事前確認**
- `git status` でコミット済みか確認
- `package.json` のバージョンをチェック
2. **ビルド & テスト**
```bash
npm ci
npm run lint
npm test
npx gulp bundle --ship
npx gulp package-solution --ship
|
デプロイ前確認
- ビルドエラーがないこと
sharepoint/solution/*.sppkg が生成されていること- ユーザーに「デプロイしてよいか」確認する
SPO へデプロイ(m365 CLI使用)
1
2
3
4
5
6
7
8
9
10
| # ログイン状態確認
m365 status
# App Catalog へアップロード
m365 spo app add --filePath ./sharepoint/solution/*.sppkg \
--appCatalogUrl $SPO_APP_CATALOG_URL --overwrite
# アプリを展開
m365 spo app deploy --name <package-name> \
--appCatalogUrl $SPO_APP_CATALOG_URL
|
動作確認
1
| npx playwright test e2e/spfx-smoke.spec.ts --headed
|
結果報告
- デプロイ成功/失敗を報告
- Playwrightのスクリーンショットがあれば確認
---
## Step 6: ローカル開発での m365 CLI セットアップ
```bash
# インストール
npm install -g @pnp/cli-microsoft365
# 対話的ログイン(ローカル開発用)
m365 login
# 現在の接続状態確認
m365 status
# App Catalog 一覧確認
m365 spo app list --appCatalogUrl https://tenant.sharepoint.com/sites/appcatalog
# 手動デプロイの例
m365 spo app add \
--filePath ./sharepoint/solution/your-webpart.sppkg \
--appCatalogUrl https://tenant.sharepoint.com/sites/appcatalog \
--overwrite
m365 spo app deploy \
--name your-webpart.sppkg \
--appCatalogUrl https://tenant.sharepoint.com/sites/appcatalog
m365 spo app install \
--id <app-id> \
--siteUrl https://tenant.sharepoint.com/sites/test
Step 7: 環境変数の管理
プロジェクトルートに .env.local(gitignore 済み)を作成:
1
2
3
4
5
6
| # .env.local
AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CERT_THUMBPRINT=ABC123...
SPO_APP_CATALOG_URL=https://tenant.sharepoint.com/sites/appcatalog
SPO_TARGET_SITE_URL=https://tenant.sharepoint.com/sites/test
|
.gitignore に追加:
.env.local
*.pem
*.pfx
*.key
sharepoint/solution/*.sppkg
トラブルシューティング
m365 login でエラーが出る場合
1
2
3
| # トークンキャッシュをクリア
m365 logout
m365 login --authType browser # ブラウザ認証で再試行
|
App Catalog が見つからない場合
1
2
| # テナントのApp Catalog URLを確認
m365 spo tenant appcatalogurl get
|
Playwright が認証できない場合
SPOはモダン認証(MSAL)を使用しているため、Cookie ベースの認証が必要です。
playwright-msal などのヘルパーライブラリの活用を検討してください。
GitHub Actions で Sites.FullControl.All が拒否される場合
Azure AD の API アクセス許可で「管理者の同意」が完了しているか確認してください。
まとめ
この構成により、以下がすべて自動化されます:
| 作業 | 自動化前 | 自動化後 |
|---|
| ビルド & テスト | 手動 gulp コマンド | GitHub Actions / /deploy-spfx |
| アプリカタログへのアップロード | ブラウザでGUI操作 | m365 spo app add |
| 「信頼して展開」クリック | ブラウザでGUI操作 | m365 spo app deploy |
| サイトへのインストール | ブラウザでGUI操作 | m365 spo app install |
| 動作確認 | 手動でブラウザ確認 | Playwright スモークテスト |
Claude Code の /deploy-spfx コマンドを使えば、ローカル開発中でもワンコマンドで全行程を実行できます。
参考リンク