mcpField
C
commerce-app-admin-ui

commerce-app-admin-ui

github.com/adobe開発316282 回インストール今日更新
リポジトリ

commerce-app-admin-ui について

commerce-app-admin-uiは、Adobe CommerceアプリのAdmin UI拡張を設定するスキルです。アプリ設定と生成される拡張ひな形を使い、グリッド列、アクション、注文ボタン、メニュー、カスタムACLリソースを追加できます。

主な機能

  1. 01注文、商品、顧客のグリッド列を設定
  2. 02一括アクションと注文表示ボタンを追加
  3. 03Admin UIのメニュー項目を追加
  4. 04カスタムACLリソースを宣言
  5. 05フロントエンドのルートとワーカーハンドラーを生成

活用シーン

  1. 01Commerce管理グリッドに独自データを追加
  2. 02管理対象の一括操作を作成
  3. 03Admin UIメニューからアプリ画面を開く
  4. 04アプリ固有の管理権限を定義

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/adobe/skills --skill commerce-app-admin-ui

インストール後、Claude Code で/commerce-app-admin-uiを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
custom-acl.md
grid-columns.md
mass-actions.md
menu.md
order-view-buttons.md

詳細

Commerce アプリの管理 UI を設定する

既存の app.commerce.config.ts の adminUi ブロックを追加または変更します。 Admin UI 拡張ポイント(commerce/backend-ui/2)を使用すると、Commerce アプリからカスタムグリッド列、一括操作、注文ビューのボタン、メニュー項目、カスタム ACL リソースを Commerce Admin に追加できます。 その他の拡張領域(webhook、イベント、ビジネス設定)は、それぞれ専用のスキルで個別に追加します。

前提条件

  • 設定ファイルが存在するだけでなく、アプリのスキャフォールディングと初期化が完了していることを確認します。以下の両方が必要です。
    • プロジェクトのルートに app.commerce.config.ts があること。
    • プロジェクトが初期化済みであること — 生成された src/commerce-extensibility-1/ ディレクトリとインストール済みの node_modules(@adobe/aio-commerce-lib-app 依存関係)で確認できます。
  • app.commerce.config.ts が存在しない場合は、停止して最初に commerce-app-init を実行します(設定ファイルを作成した後、初期化を実行します)。
  • 設定ファイルは存在するものの、プロジェクトが初期化されていない場合(src/commerce-extensibility-1/ または node_modules がない場合)は、続行する前に npx @adobe/aio-commerce-lib-app init を実行します。init は冪等です。既存の設定ファイルを検出して対話式プロンプトを省略し、依存関係をインストールしてプロジェクトファイルを生成します。
  • アクションを TypeScript で作成できるのは、TypeScript Commerce 設定用に init がスキャフォールディングする TypeScript ビルド設定(webpack-config.cjs とルートの tsconfig.json)がプロジェクトにある場合のみです。詳細は commerce-app-init を参照してください。それ以外の場合は、アクションを JavaScript で作成します。

拡張ポイントの概要

拡張ポイントエンティティバリアントサーバーハンドラーリファレンス
グリッド列注文、商品、顧客worker のみはいgrid-columns
一括操作注文、商品、顧客view / workerworker のみmass-actions
注文ビューのボタン注文のみview / workerworker のみorder-view-buttons
メニュー単一項目(adminUi.menu)view(iframe)いいえmenu
カスタム ACLリスト(adminUi.acl)なしいいえcustom-acl

view は、エントリーの path にあるアプリの Web UI(web-src)を iframe として表示します。worker はランタイムアクションをサーバー側で呼び出します。 カスタム ACL リソースにはバリアントもハンドラーもありません。Commerce がユーザーロールのツリーに表示する独立した権限であり、アプリ自身が確認します(custom-acl を参照)。 グリッド列は常に worker です。メニューは常に iframe です。

ステップ 1 — 意図を把握する

ユーザーが追加したい各項目について、次の情報を収集します。

  • 拡張ポイント — グリッド列、一括操作、注文ビューのボタン、メニュー、カスタム ACL リソースのいずれか
  • エンティティ — order、product、customer のいずれか(グリッド列と一括操作の場合。ビューのボタンは注文のみ。メニューとカスタム ACL リソースにはエンティティがありません)
  • 一括操作とビューのボタンの場合は、バリアント — worker(ランタイムアクション)または view(web-src の iframe)
  • 拡張ポイントのフィールド(列の定義、ボタンのラベル、メニューの親項目など)— すべてのフィールドについては、上の表にあるリファレンスファイルを参照してください

ステップ 2 — app.commerce.config.ts で宣言する

最上位の adminUi ブロックを追加するか、既存のブロックにマージし、他のすべてのドメインを保持します。すでに adminUi が存在する場合は、置き換えずにマージしてください。既存のエンティティ、メニュー、配列の既存エントリを保持します。

これらのフィールドは拡張ポイント間で共通です。

フィールド制約
runtimeAction<package>/<action>。宣言するハンドラーアクション(ステップ 4)を解決できる必要があります。
confirm任意の { title?, message? } — アクションの実行前に確認ダイアログを表示します。
notifications任意の { success?, error? } — 完了時に Commerce が表示するトーストテキストです。
sandboxPermissions任意の配列(view/iframe のエントリ)。空でないこと、重複がないこと。各項目は allow-downloads、allow-modals、allow-popups のいずれかです。

各機能を含む最小限の例です(必要な部分のみ使用してください)。

import { MENU_SALES } from "@adobe/aio-commerce-sdk/admin-ui/menu";

// inside defineConfig({ ... }):
adminUi: {
  order: {
    // Custom column on the order grid (worker only)
    gridColumns: {
      label: "Fulfillment data",
      description: "Fulfillment status from the warehouse system.",
      runtimeAction: "my-app/order-grid", // <package>/<action> — declare in Step 4
      columns: [
        { id: "fulfillment_status", label: "Fulfillment", type: "string", align: "left" },
      ],
    },
    // Bulk action on selected orders (worker variant shown)
    massActions: [
      { type: "worker", id: "archive-orders", label: "Archive",
        runtimeAction: "my-app/archive-orders", selectionLimit: 500 },
    ],
    // Button on the order view page (worker variant shown)
    viewButtons: [
      { type: "worker", id: "sync-inventory", label: "Sync inventory",
        runtimeAction: "my-app/sync-inventory" },
    ],
  },
  // Custom Admin menu entry (iframe into the app)
  menu: {
    id: "my_app_dashboard", // letters, digits, / : _ only
    label: "My Dashboard",
    description: "Custom dashboard for my app.",
    parentMenu: MENU_SALES,
  },
  // Custom ACL resources — standalone permissions the app checks itself (no handler)
  acl: [
    { id: "reports", label: "Reports", children: [
      { id: "export", label: "Export" },
    ]},
    { id: "approve_refunds", label: "Approve Refunds" },
  ],
}

view のバリエーション(iframe)、および各拡張ポイントのフィールドと制約の全容については、記述する前に References にある該当するリファレンスファイルを確認してください。

ステップ 3 — 拡張ポイントを登録する

init を実行して、commerce/backend-ui/2 を app.config.yaml と install.yaml に追加し、src/commerce-backend-ui-2/ 拡張フォルダーを生成します。これはべき等なので、拡張機能がすでに登録されている場合でも安全に実行できます。

npx @adobe/aio-commerce-lib-app init

ビルドでは、adminUi 設定から拡張機能の ext.config.yaml が生成されます。各ワーカーの runtimeAction は workerProcess オペレーションになり、menu または view タイプのエントリを宣言すると、view オペレーションと明示的な web: web-src キーが書き込まれます。hooks、operations、web セクションはライブラリが管理するため、手動で編集しないでください。

view オペレーションが存在する場合、init/generate によって Web フロントエンドも自動的にスキャフォールドされます(web-src/index.html がすでに存在する場合はスキップされます)。src/commerce-backend-ui-2/web-src/ に index.html、src/app.jsx、src/pages/main-page.jsx、src/components/welcome.jsx(アプリ設定が TypeScript の場合は .tsx と独立した tsconfig.json)が生成され、package.json に #web/* インポートエイリアスが追加されます。また、固定バージョンの react、react-dom、@react-spectrum/s2、@adobe/aio-commerce-lib-admin-ui(React と Spectrum S2 は admin-ui ライブラリのオプションのピア依存関係です)が宣言され、インストールされます。さらに、適切な TypeScript および Babel のサポート/設定のための devDependencies も追加されます。別の必須ファイルフェーズが生成のたびに実行され、既存バージョンを置き換えずに一連の Web ソースサポートファイルが存在することを確認します。現在の対象には web-src/.babelrc が含まれます。このファイルは各環境で React の自動 JSX 変換を選択し、開発ビルドでは JSX 診断を維持し、本番ビルドでは jsxDEV 呼び出しを出力しないようにします。BABEL_ENV が設定されている場合は、NODE_ENV と同期させてください。設定環境の選択では、Babel は BABEL_ENV を優先するためです。TypeScript のスキャフォールドでは、プロジェクトの合成された typecheck スクリプトに typecheck:web-src も追加されます。これらの依存関係について、異なるバージョンを独自に選ばないでください。互換性のないバージョンがすでにインストールされている場合、スキャフォールドは失敗します。

web-src がすでに存在するためスキャフォールドがスキップされた場合は、その package.json を確認し、@react-spectrum/s2 ではなく、従来の React Spectrum(@adobe/react-spectrum または s2 を含まない @react-spectrum/<component>)が使われていないか確認してください。両者には互換性がありますが、Adobe が移行を推奨しているのは S2 であり、このスキャフォールドも S2 を対象としています。アップグレードを提案してください。このスキルは Admin UI 拡張機能を設定するだけで、アップグレード自体は実行しません。実際の移行については、ユーザーに commerce-app-migrate スキルを案内してください。

npx skills add adobe/skills --skill commerce-app-migrate

ユーザーから依頼されない限り、インストールやアップグレードは自分で実行しないでください。

ステップ 4 — ハンドラーを実装する

実装内容はバリアントによって異なります。以下の例は TypeScript です。プロジェクトで JavaScript を使用している場合は、型のインポートと型注釈を省略してください。

ワーカーバリアント(グリッド列、ワーカーの一括アクション、ワーカーのビュー操作ボタン)

各ワーカーの runtimeAction には、Admin UI 拡張フォルダー src/commerce-backend-ui-2/ 内に宣言して実装するアクションが必要です。

  1. ハンドラーのソースを src/commerce-backend-ui-2/actions/ に配置します。
  2. src/commerce-backend-ui-2/ext.config.yaml の runtimeManifest 内にある独自のパッケージの下で宣言します(パッケージ名は任意です)。ビルドではこのファイルの hooks と operations が管理されますが、runtimeManifest の下に追加したパッケージは保持されるため、再ビルド後もアクションは残ります。
# src/commerce-backend-ui-2/ext.config.yaml
runtimeManifest:
  packages:
    my-app: # must match the <package> in runtimeAction
      actions:
        order-grid:
          function: actions/order-grid/index.js # relative to src/commerce-backend-ui-2/
          web: "yes"
          runtime: nodejs:24
          annotations:
            require-adobe-auth: true # Commerce calls the action with an IMS token — validate it
            final: true

runtimeAction の <package>/<action> はそのまま対応します。my-app/order-grid → パッケージ my-app、アクション order-grid です。Commerce はこれらのワーカーアクションを Adobe IMS トークンを使って呼び出すため、require-adobe-auth: true(およびバインドされた入力を固定するための final: true)が必要です。

@adobe/aio-commerce-sdk/admin-ui/* のエントリーポイントから、ワイヤーコントラクトビルダーを使ってハンドラーを実装します。 ビルダーは拡張ポイントごとに異なります。リファレンスファイルに、正確なリクエスト形式とレスポンスビルダーが記載されています。グリッド列の例:

// src/commerce-backend-ui-2/actions/order-grid/index.ts
import {
  parseGridRequest,
  okGridResponse,
  errorGridResponse,
} from "@adobe/aio-commerce-sdk/admin-ui/grid-columns";
import type { RuntimeActionParams } from "@adobe/aio-commerce-sdk/core/params";

export async function main(params: RuntimeActionParams) {
  const { gridType, ids } = parseGridRequest(params);
  try {
    const rows = await fetchRows(gridType, ids);
    // row keys must match the column ids declared in config
    return okGridResponse(rows, { fulfillment_status: "unknown" });
  } catch (error) {
    return errorGridResponse(
      500,
      error instanceof Error ? error.message : String(error),
    );
  }
}
ビューのバリアント(ビューの一括アクション、ビューボタン、メニュー)

サーバーハンドラーはありません。Commerce は、エントリーの path にあるアプリの web-src を iframe で開きます。このフロントエンドはステップ 3 で生成されています(index.html、src/app.jsx、src/pages/main-page.jsx、src/components/welcome.jsx)。生成された src/app.jsx は、@adobe/aio-commerce-lib-admin-ui/web の createExtensionApp を使用して commerce/backend-ui/2 の iframe アプリをマウントします。また、その routes 配列では、インデックスルートを先頭にする必要があります。

// src/commerce-backend-ui-2/web-src/src/app.jsx (generated)
import { createExtensionApp } from "@adobe/aio-commerce-lib-admin-ui/web";
import "@react-spectrum/s2/page.css";

import config from "#app.commerce.config";
import { MainPage } from "#web/pages/main-page.jsx";

createExtensionApp({
  metadata: { extensionId: config.metadata.id },
  routes: [{ index: true, element: <MainPage /> }],
});

ワーカーバリアントで runtimeAction をアクションに接続するのと同様に、各 view エントリーの path をページとルートに接続する必要があります。エントリーごとにその接続をスキャフォールドします。ただし、不足しているものだけを作成してください。既存のページやルートを上書きしたり変更したりしないでください。

メニューには path がありません。インデックスルート(src/pages/main-page.jsx)にレンダリングされます。このページはスキャフォールドによって、すでにプレーンなページとして作成されています。メニュー用にスキャフォールドするものはありません。main-page.jsx はそのまま残してください(必要であれば内容をカスタマイズできます)。設定の parentMenu には MENU_* 定数のみが必要です(menu を参照)。

path を持つ、view タイプの一括アクションと注文ビューボタンそれぞれについて、次の手順を行います。

  1. ルートを確認します。 src/app.jsx で、path がエントリーの設定 path と一致する routes エントリーを探し、web-src/src/pages/ で対応するページファイルを探します。その path のルートがすでに存在する場合は、ルートとページを変更せず、そのまま次へ進みます。

  2. プレースホルダーページを作成します(不足している場合のみ)。 web-src/src/pages/<name>.jsx を追加します。アプリ設定が TypeScript の場合は、.tsx と TypeScript を使用します。生成された main-page/welcome の見た目に合わせて、最小限の内容にします(見出しを含む <main>)。著作権ヘッダーは追加しないでください。生成された web-src ファイルにはヘッダーがありません。エントリータイプに応じたコンテキストフックをあらかじめ接続します(下の表を参照)。

  3. src/app.jsx にルートを登録します(不足している場合のみ)。 #web/pages/* エイリアスを使用してページをインポートし、routes に { path, element } エントリーを追加します。path は、先頭の #/ を含め、設定の path と完全に同じ文字列にしてください。ルートと設定エントリーが一目で対応するよう、文字列をそのままコピーします。インデックスルートは先頭に維持してください。

ビュータイプに応じて、次のフックをあらかじめ接続します。すべて @adobe/aio-commerce-lib-admin-ui/web からインポートします。

ビューエントリーコンテキストフック併用するもの参照
一括アクション(view)useMassActionContext() → { data, error }。data.selectedIds を使用useHostConnection() → { actions, error }。actions.close() を使用mass-actions
注文ビューボタンuseOrderViewButtonContext() → { data, error }。data.orderId を使用useHostConnection() → { actions, error }。actions.close() を使用order-view-buttons
メニューなし(プレーンなインデックスページ)—menu

これらのフックは、エラーをスローする代わりに返します。ルートで、レンダリング中に返されたエラーをスローすると、SDK のエラーバウンダリーにエラーが送られ、拡張機能のコンテンツがフォールバック UI に置き換わります。ルートをマウントしたままにする必要がある場合は、メッセージを表示する、再試行を可能にする、影響を受ける機能を無効にする、または別の縮退状態を提供するなど、ローカルでエラーを処理してください。以下のプレースホルダー例では、独自のリカバリー UI を定義していないため、エラーをスローします。 ルートコンポーネントでは、Commerce REST API にアクセスするために useIms() を、ホストから直接データを取得するために useCommerce() を呼び出すこともできます。どちらも { data, error } を返します。error を処理した後、useIms() からは data.imsToken と data.imsOrgId を、useCommerce() からは data.commerceHost を読み取ります。生成された Welcome コンポーネントでは、useIms() の結果の処理方法を確認できます。これらのフックは、ページで実際に必要な場合にのみ追加してください。

例 — view マスアクションのプレースホルダーページと、そのルート登録:

// src/commerce-backend-ui-2/web-src/src/pages/export-customers.jsx
import {
  useHostConnection,
  useMassActionContext,
} from "@adobe/aio-commerce-lib-admin-ui/web";

export function ExportCustomersPage() {
  const { data, error: contextError } = useMassActionContext();
  const { actions, error: hostError } = useHostConnection();
  if (contextError) throw contextError;
  if (hostError) throw hostError;

  const { selectedIds } = data; // non-empty string[] — the selected record ids
  const { close } = actions; // await close() (or actions.closeWithError()) when done

  return (
    <main>
      <h1>Export customers</h1>
      <p>{selectedIds.length} selected</p>
    </main>
  );
}
// src/commerce-backend-ui-2/web-src/src/app.jsx
import { createExtensionApp } from "@adobe/aio-commerce-lib-admin-ui/web";
import "@react-spectrum/s2/page.css";

import config from "#app.commerce.config";
import { MainPage } from "#web/pages/main-page.jsx";
import { ExportCustomersPage } from "#web/pages/export-customers.jsx";

createExtensionApp({
  metadata: { extensionId: config.metadata.id },
  routes: [
    { index: true, element: <MainPage /> }, // keep the index route first
    { path: "#/export-customers", element: <ExportCustomersPage /> }, // path === config `path`
  ],
});

注文表示ボタンの場合は、フックを useOrderViewButtonContext() に置き換え、error を処理した後に data.orderId を読み取ります。詳しくはorder-view-buttonsをご覧ください。

メニュー/表示ページから独自のアクションを呼び出す

メニューまたは表示ページでは、Commerce からデータを受け取るのではなく、独自のデータを取得する必要がある場合があります。たとえば、カスタムメニューページでテーブルを表示する場合です。その場合は、web-src からアプリ独自のアクションを直接呼び出します。

拡張機能で actions と web-src の両方を宣言すると、aio app build/aio app dev によって、各アクションの実際の URL を含む web-src/src/config.json(config.web.injectedConfig)が自動生成されます。このファイルは gitignore 対象で、ビルドのたびに再生成されるため、手動でひな形を作成する必要はありません。

// src/commerce-backend-ui-2/web-src/src/hooks/use-config.js
import { useCallback } from "react";
import actionUrls from "../config.json";

export function useConfig() {
  return { getActionUrl: useCallback((action) => actionUrls[action], []) };
}

// Usage — combine with useIms() for the auth headers:
import { useIms } from "@adobe/aio-commerce-lib-admin-ui/web";

const { data, error } = useIms();
if (error) throw error;

const response = await fetch(getActionUrl("my-app/order-grid"), {
  headers: {
    authorization: `Bearer ${data.imsToken}`,
    "x-gw-ims-org-id": data.imsOrgId, // required for require-adobe-auth: true actions, or the request fails
  },
});

ステップ 5 — 検証

プロジェクトをビルドして、更新した設定が有効であることを確認します。

aio app build

ビルド時に検証エラーが発生した場合は、問題のある adminUi フィールドが直接示されます。

よくある問題

  • view と worker の不一致:各バリアントは厳密なオブジェクトです。worker エントリーには runtimeAction が必要で、path/sandboxPermissions は指定できません。view エントリーには path が必要で、runtimeAction/timeout は指定できません。両者は type で判別されます。
  • グリッド列は worker 専用:view のグリッド列はありません。gridColumns をサポートするのは order、product、customer のみで、viewButtons をサポートするのは order のみです。
  • ハンドラーがない runtimeAction:<package>/<action> が src/commerce-backend-ui-2/ext.config.yaml の runtimeManifest に宣言されていない worker エントリーは、デプロイ時に生成された workerProcess の参照が解決されない状態になります。
  • アクションの配置場所が誤っている:ハンドラーのソースと runtimeManifest のエントリーは、Admin UI 拡張機能フォルダー src/commerce-backend-ui-2/ に置きます。Webhook やイベントのハンドラーを置く src/commerce-extensibility-1/ ではありません。function のパスは src/commerce-backend-ui-2/ からの相対パスです。
  • グリッドの行キーは列 ID と一致させる必要がある:okGridResponse の行のキーは gridColumns.columns の id と一致している必要があります。一致しない場合、セルは空で表示されるか、デフォルトの値の集合が使用されます。
  • 表示ルートの path:src/app.jsx に登録する { path } は、エントリー設定の path とまったく同じ文字列にしてください。ハッシュも含めてそのままコピーし、両者を一致させます。
  • メニューの id で使用できる文字:メニューの id で使用できるのは英字、数字、/、:、_ のみです。ハイフンやスペースは使用できません。
  • defineConfig が見つからない:@adobe/aio-commerce-lib-app/config から defineConfig をインポートしてください。
  • 開発中にレンダリング/リクエストが重複する:aio app dev および aio app run で提供されるローカルビルドは開発用ビルドです。createExtensionApp はアプリを React の <StrictMode> でラップするため、コンポーネントは 2 回レンダリングされ、マウント時のエフェクトはセットアップとクリーンアップを追加で 1 サイクル実行します。ローカル開発中にレンダリングが重複したり、エフェクトによるリクエストが重複したりするのは、StrictMode の想定された動作であり、修正すべきバグではありません。aio app build と aio app deploy では、本番用ビルドが生成され、<StrictMode> はバンドルから除去されるため、ラップなしでレンダリングされます。

品質基準

  • aio app build がエラーなく完了する
  • すべての worker runtimeAction に対応するアクションが、src/commerce-backend-ui-2/ext.config.yaml の runtimeManifest に宣言されている

連携

aio app build が成功した後に、次の操作を行います。

  • マーチャント設定の追加 — commerce-app-business-config を呼び出して、Commerce Admin に設定可能な設定項目を公開します
  • Webhook インターセプターの追加 — commerce-app-webhooks を呼び出して、Commerce の操作をインターセプトします
  • イベントサブスクリプションの追加 — commerce-app-eventing を呼び出して、Commerce または外部のイベントをサブスクライブします
  • 永続ストレージの追加 — commerce-app-storage を呼び出して、クエリ可能な DB ストレージでワーカーのアクションを支えます

参考資料

コメント