setup-solution について
setup-solutionは、Power Pagesプロジェクト向けのALMスキルです。Dataverseのパブリッシャーとソリューションを作成してサイトコンポーネントを追加し、後続のエクスポート、インポート、パイプライン用マニフェストを出力します。
主な機能
- 01Dataverseのパブリッシャーとソリューション作成
- 02Power Pagesコンポーネントの追加
- 03既存ソリューションとの同期
- 04ソリューションマニフェストの出力
- 05複数ソリューション計画への対応
活用シーン
- 01Power Pagesプロジェクトのソリューション展開準備
- 02既存ソリューションとサイトコンポーネントの同期
- 03分割計画に基づく複数ソリューションの作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill setup-solutionインストール後、Claude Code で/setup-solutionを入力して読み込まれたか確認します。
同梱ファイルscripts/validate-solution.js
詳細
プラグインチェック:
node "${PLUGIN_ROOT}/scripts/check-version.js"を実行してください — メッセージが出力された場合は、続行する前にユーザーに表示してください。
setup-solution
Dataverse の発行元とソリューションを作成し、Power Pages サイトのコンポーネントを追加します。export-solution、import-solution、setup-pipeline スキルで使用する .solution-manifest.json を書き込みます。
前提条件
- PAC CLI がインストールされ、認証済みであること(
pac env whoが環境 URL を返す) - Azure CLI がインストールされ、ログイン済みであること(
az account showが成功する) - プロジェクトルートに
powerpages.config.jsonが存在すること(コンポーネントレコードを含む.powerpages-site/が存在するように、サイトが少なくとも一度はデプロイされている必要があります)
フェーズ
フェーズ 0 — ALM プランゲート
plan-almが正面玄関です。 ユーザーが ALM の意図(昇格 / リリース / デプロイ / CI-CD のセットアップ / ステージングへの移動 / 本番へのプッシュ)を示した場合、オーケストレーター(/power-pages:plan-alm)を最初に実行する必要があります。このフェーズ 0 はそれを強制するものであり、プランがない場合はクローズドに失敗するように設計されており、ユーザーが永遠に却下できる一度きりのチェックではありません。
スキップ規則。 このスキルがアクティブな plan-alm オーケストレーションの一部として呼び出された場合は、フェーズ 0 を完全にスキップしてフェーズ 1 に進みます。ゲートヘルパーはこれを inExecution ブロックで公開します — 次の場合にはフェーズ 1 へサイレントにパススルーします:
inExecution.status === "active"
このヘルパーは docs/.alm-plan-data.json からこれを計算します — PLAN_STATUS === "In Execution" かつ LAST_INVOCATION_AT が過去 60 分以内であること。check-alm-plan.js は、プランが実行中であることを検出したすべての呼び出しで LAST_INVOCATION_AT を自動的に更新します。そのため、チェーン内の各スキルは次のスキルのためにチェーンを存続させ続けます — 数時間におよぶデプロイ(deploy-pipeline だけでステージごとに 60 分かかることがあります)であっても、チェーンが誤って分類解除されることなく、この時間枠を生き延びます。停滞したチェーン(60 分を超えてハートビートがない)は stale-heartbeat として再分類され、フェーズ 0 のゲートが通常どおり発動するため、放棄されたプランがユーザーの確認をサイレントに迂回することはありません。
inExecution.status が "active" 以外("not-running"、"stale-heartbeat"、"no-plan")である場合は、以下のフェーズ 0 ゲートフローを実行します。残りのヘルパーフィールドで分岐します:
ステップ 1 — ゲートヘルパーを実行します。
node "${PLUGIN_ROOT}/scripts/lib/check-alm-plan.js" --projectRoot "."
このヘルパーは { exists, deferred, stale, staleness: { reason, detail }, generatedAt, planStatus, ... } を含む JSON を返します。同期モード(.solution-manifest.json がすでに存在する場合)では、フェーズ 1 で取得された後に --envUrl、--token、--solutionId を追加で渡すことができますが、最初のゲートでは存在確認のみで十分です。
ステップ 2 — 結果に応じて分岐します。
| 結果 | 動作 |
|---|---|
deferred: true | ユーザーがこのプロジェクトの ALM を明示的に延期しています(.alm-deferred マーカーが存在します)。フェーズ 1 へサイレントにパススルーします — 催促しないでください。 |
exists: false | ユーザーがまだ plan-alm を実行していません。ステップ 3 を参照してください。 |
exists: true, stale: false | プランは最新です。フェーズ 1 へサイレントにパススルーします。 |
exists: true, stale: true(理由: solution-modified) | プラン生成後にソリューションが変更されました。ステップ 4 を参照してください。 |
ステップ 3 — プランなし。 ユーザーに伝えます:
「このプロジェクトには ALM プランが存在しません。
/power-pages:plan-almがプランを作成します — プロジェクトの状態を検出し、昇格戦略(PP Pipelines か手動エクスポート/インポートか)について尋ね、適切なスキル(これを含む)を適切な順序でオーケストレーションします。今すぐ plan-alm を実行しますか?」
🚦 ゲート(意図 · setup-solution:0.no-plan):
check-alm-plan.jsがexists:falseを返した場合のクローズドに失敗するエントリゲート。ヘルパースクリプトに裏付けられています。
AskUserQuestion:
| 質問 | ヘッダー | オプション |
|---|---|---|
先に /power-pages:plan-alm を実行しますか? | ALM プランゲート | はい — 今すぐ /power-pages:plan-alm を実行する(推奨)、プランなしで続行する(上級 — 何をしているか理解しています)、キャンセル |
- はい(推奨) →
/power-pages:plan-almを実行します。計画を作成して結果を返します。plan-almはプランナーであり、デプロイは行いません。このスキルはその後、フェーズ 0 のチェックを再実行し(この時点でexists:trueになります)、フェーズ 1 に進みます。 - 計画なしで続行 →
BYPASSED_PLAN_GATE = trueに設定し、フェーズ 1 に進みます。 - キャンセル → 正常に終了します。
ステップ 4 — 古い計画。 ユーザーに次の内容を伝えます。
「
{generatedAt}に ALM 計画が作成されていますが、その後ソースソリューションが変更されています({solution.modifiedon}時点)。コンポーネントが変更されている可能性があります。plan-almを再実行すると、分析とレンダリング済み HTML が更新されます。」
🚦 ゲート(意図 · setup-solution:0.stale-plan):
check-alm-plan.jsがstale:trueを返した場合の、フェイルクローズ方式の開始ゲートです。ヘルパースクリプトに基づきます。
AskUserQuestion:
| 質問 | ヘッダー | 選択肢 |
|---|---|---|
| まず計画を更新しますか? | ALM 計画の鮮度 | 更新 — /power-pages:plan-alm を再実行(推奨)、既存の計画で続行、キャンセル |
- 更新(推奨) →
/power-pages:plan-almを実行します。完了後、フェーズ 0 のヘルパーをもう一度実行して計画が最新であることを確認します。それでも古い場合は詳細を提示し、フェーズ 1 に進みます(無限ループにはしません)。 - 続行 →
STALE_PLAN_ACK = trueに設定し、フェーズ 1 に進みます。 - キャンセル → 正常に終了します。
このゲートがある理由。 setup-solution を直接実行すると、オーケストレーターの計画を参照せずにソリューションが作成(または同期)されます。計画がすでに存在し、複数ソリューションへの分割が推奨されている場合、このスキルを単独で実行すると、コンポーネントが誤ったベースソリューションに統合される可能性があります。まだ計画がない場合、ソリューションを作成する前に plan-alm が分割の推奨事項、アセットサイズに関する注意事項、不足しているコンポーネントを提示します。先に setup-solution を実行すると、こうした判断を知らないまま進めてしまいます。このゲートにより、ユーザーが一時的なソリューションを作成したいと明確に理解している場合の明示的なバイパスを残しつつ、setup-solution を適切なコンテキストで実行できます。
フェーズ 1 — 前提条件の確認
このフェーズの開始時に、すべてのタスクをあらかじめ作成します。
作成するタスク:
- 「前提条件の確認」
- 「ソリューション設定の収集」
- 「既存のパブリッシャーとソリューションの確認」
- 「パブリッシャーとソリューションの作成」
- 「サイトコンポーネントのソリューションへの追加」
- 「マニフェストの確認と書き込み」
- 「概要の提示」
手順:
-
pac env whoを実行し、environmentUrlとorganizationIdを抽出します(ユーザーに確認のため表示します)。 -
verify-alm-prerequisites.jsを実行して PAC CLI の認証を確認し、トークンを取得して、API アクセスを検証します。node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" --envUrl "{environmentUrl}"出力を JSON として取得し、
.envUrl(envUrlとして保存)と.token(tokenとして保存)を抽出します。スクリプトがゼロ以外の値で終了した場合は停止し、不足しているものを説明します(${PLUGIN_ROOT}/references/dataverse-prerequisites.mdを参照してください)。 -
powerpages.config.jsonを見つけ、siteNameとwebsiteRecordIdを読み取ります。 -
.powerpages-site/フォルダーが存在することを確認します(コンポーネントレコードの検索に必要です)。 -
ALM 計画のコンテキストを確認 —
docs/alm/alm-plan-context.jsonがあるかを確認します。🚦 ゲート(計画 · setup-solution:1.preloaded): あらかじめ読み込まれた計画の分類を使うか、再検出するかを選択します。この選択前に書き込みは行われません。
- 見つかった場合は、
AskUserQuestionで質問します。"このサイトの ALM プランは以前に生成されています。このプランには、サイト設定の事前分類済みリスト(keepAsIs、promoteToEnvVar、authNoValue、excluded)が含まれています。これらの選択内容を使用しますか。それとも、すべてを再検出して再分類しますか?"
- 選択肢:「プランに事前読み込みされた選択内容を使用」/「再検出して再分類」
- ユーザーが事前読み込みされた選択内容を選んだ場合:
docs/alm/alm-plan-context.jsonを読み込み、siteSettingsオブジェクトをpreloadedSettingsとして保存します。Step 5.3 に到達したら、クエリと分類のロジックをスキップし、preloadedSettingsを直接使用します。 - ユーザーが再検出を選んだ場合:通常どおり続行します(Steps 5.3–5.4 で Dataverse にクエリし、再分類します)。
- 見つかった場合は、
-
同期モードを検出する — プロジェクトのルートに
.solution-manifest.jsonが存在するか確認します。-
存在する場合:読み込み、次の
GET {envUrl}/api/data/v9.2/solutions({solutionId})?$select=solutionid,uniquename,version,ismanagedを使用して、対象環境にsolutionIdがまだ存在することを確認します。- ソリューションがこの環境にまだ存在し、アンマネージドの場合:
syncMode = trueを設定し、existingSolution= マニフェストの内容を保存します。
🚦 ゲート(同意・setup-solution:1.stale-manifest): マニフェストが、現在の環境にないソリューションを参照しています。最初からやり直す(マニフェストをバックアップして新しいソリューションを作成する)か、中止してください。
- ソリューションが見つからない場合、マネージドの場合、または別の環境にある場合:古いマニフェストとして扱い、ユーザーにその旨を伝え、
AskUserQuestionで質問します。"既存の
.solution-manifest.jsonは{uniqueName}v{version} のソリューションを参照していますが、現在の環境で見つかりませんでした。次のどちらにしますか? 1)最初からやり直す(マニフェストをバックアップして新しいソリューションを作成する)、2)調査のため中止する" 明示的な選択を受けてから続行します。
- ソリューションがこの環境にまだ存在し、アンマネージドの場合:
-
存在しない場合:
syncMode = falseを設定します — これは新規セットアップです。
-
-
選択されたモードをユーザーに報告します。
syncMode = true:「既存のソリューション{uniqueName}v{version} が見つかりました。同期モードで実行します — 現在のサイトのインベントリを検出し、既存のソリューションとの差分を確認して、不足しているコンポーネントのみ追加します。」syncMode = false:「既存のソリューションマニフェストが見つかりませんでした。新規セットアップとして実行します — パブリッシャーとソリューションを作成してから、すべてのサイトコンポーネントを追加します。」
-
分割プランを確認する(複数ソリューションモード) —
docs/alm/alm-split-plan.json(plan-almPhase 1 Step 10 で書き込まれる)を探します。- 見つかり、
proposedSolutions.length > 1の場合は、MULTI_SOLUTION_MODE = trueを設定し、配列をPROPOSED_SOLUTIONSとして保存します。 - 複数ソリューションモードの場合:
- Phase 2 では、パブリッシャーの詳細を一度だけ尋ね(すべてのソリューションで共有)、提案されたソリューション名/バージョンを確認用に提示します(ユーザーは続行前にそれぞれを変更できます)。
- Phase 4 では、まずパブリッシャーを作成し(すべてのソリューションがこれにバインドされるため、単一の直列ステップ)、次に
PROPOSED_SOLUTIONS内のソリューションを並列に作成します。orderフィールドは後続のパイプライン段階の順序付けに使用するデータであり、各ソリューションは独立している(異なるuniqueName、共有のpublisherId、ソリューション間の依存関係なし)ため、作成順序を制約するものではありません。 - Phase 5 では、
proposedSolutions[i].componentTypesとtableLogicalNames(Strategy 3 の場合)に基づいて、ソリューションごとにAddSolutionComponent呼び出しを振り分けます。 - Phase 6 では、マニフェスト v2 を書き込みます(下記参照)。
- 見つからない場合、または
proposedSolutions.length === 1の場合は、単一ソリューションモード(既存のフロー)で続行します。
- 見つかり、
フェーズ 1.5 — 現在の ALM ドキュメントに基づくグラウンディング
参照:
${PLUGIN_ROOT}/references/alm-docs-grounding.md
このステップは約 30 秒を上限とします。MCP の検索 / 取得がエラーになった場合は、1 行のメモをログに記録して続行してください — このスキルはオフラインでも実行可能でなければなりません。
microsoft_docs_searchをクエリPower Pages solution publisher creation Dataverse component types ALMで実行します。https://learn.microsoft.com/en-us/power-platform/alm/solution-concepts-almを取得します(検索で関連する新しいチュートリアル — たとえばマルチソリューションのレイヤリング、マネージドプロパティのガイダンス — が見つかった場合は、姉妹ページを最大 1 つまで)。これらはmicrosoft_docs_fetchで並列に取得します。- ソリューションコンポーネント、パブリッシャープレフィックスの不変性、マネージドとアンマネージドの選択、およびコンポーネントタイプの整数について、Microsoft Learn が現在述べている内容を 1 段落の要約として抽出します。
${PLUGIN_ROOT}/references/solution-api-patterns.mdと比較し、相違点(新しいコンポーネントタイプ、変更されたアクションシグネチャ、非推奨となったパターン)があれば指摘します。 - この要約をフェーズ 2 以降の判断に活用します。スキルの動作を暗黙に変更しないでください — 相違点があれば、フェーズ 4(パブリッシャーとソリューションの作成)の前にソフト警告としてユーザーに提示してください。
フェーズ 2 — ソリューション構成の収集
syncMode = trueの場合はこのフェーズ全体をスキップします。 代わりにマニフェストのexistingSolution.publisherとexistingSolution.solutionを使用してください。フェーズ 5 に進みます。
🚦 ゲート(同意 · setup-solution:2.publisher-prefix): パブリッシャープレフィックスは永続的であり、すべてのコンポーネントの論理名にプレフィックスとして付与されます。明示的に確認する必要があります。キャンセルすると、パブリッシャー / ソリューションの書き込み前に終了します。
ユーザーに(AskUserQuestion を使用して)以下を尋ねます:
- パブリッシャーの一意の名前(例:
contoso) — 小文字の英字 / 数字のみ、スペースは使用できません。これが永続的であり、変更できないことを説明してください。 - パブリッシャーのフレンドリ名(例:
Contoso) — 表示名 - パブリッシャープレフィックス(例:
con) — 小文字の英字 2〜8 文字、すべてのコンポーネントにプレフィックスとして付与されます。これが永続的であり、変更できないことを説明してください。 - ソリューションの一意の名前(例:
ContosoSite) — 英字 / 数字 / アンダースコア、スペースは使用できません - ソリューションのフレンドリ名(例:
Contoso Site) — 表示名 - ソリューションのバージョン(デフォルト:
1.0.0.0) —major.minor.build.revision形式である必要があります
すべての値の確認サマリーを提示し、続行する前にユーザーの承認を待ちます。
重要な判断ポイント: パブリッシャープレフィックスとパブリッシャーの一意の名前は取り消しできません — 続行する前に一時停止し、ユーザーに明示的に確認してください。
フェーズ 3 — 既存の状態の確認
syncMode = trueの場合はこのフェーズ全体をスキップします。 マニフェストによってソリューションが存在することが保証されており、フェーズ 1 のステップ 6 ですでに検証済みです。
何かを作成する前に、パブリッシャーとソリューションがすでに存在するかどうかを確認します:
- パブリッシャーを照会します:
GET {envUrl}/api/data/v9.2/publishers?$filter=uniquename eq '{publisherUniqueName}'&$select=publisherid,uniquename,customizationprefix(パブリッシャー専用のスクリプトはありません — OData エンドポイントを直接照会してください。) verify-solution-exists.jsを使用してソリューションの存在を確認します:
出力を JSON としてキャプチャし、node "${PLUGIN_ROOT}/scripts/lib/verify-solution-exists.js" \ --envUrl "{envUrl}" \ --uniqueName "{solutionUniqueName}" \ --token "{token}".found(ブール値)を確認します。foundの場合は、表示用に.solutionId、.version、.isManagedも読み取ります。
調査結果をユーザーに報告します:
- パブリッシャーが存在する場合: 「既存のパブリッシャー
{name}(プレフィックス:{prefix})が見つかりました。これを再利用します。」 - ソリューションが存在する場合: 「既存のソリューション
{name}バージョン{version}が見つかりました。これを再利用し、コンポーネントを追加します。」 - どちらも存在しない場合: 「新しいパブリッシャーとソリューションを作成します。」
続行する前にユーザーの確認を待ちます。
フェーズ 4 — パブリッシャーとソリューションの作成
syncMode = trueの場合、このフェーズ全体をスキップしてください。 パブリッシャーとソリューションはすでに存在します。同期モードでのバージョン更新:フェーズ 5 で追加操作を行う前に、既存ソリューションのパッチセグメントを更新し、同期後のマニフェストと、その後のエクスポートが以前のバージョンを確実に上書きできるようにします。共有ヘルパーを使用してください。更新ルールの唯一の信頼できる情報源です(不足しているセグメントはゼロで補完し、整数として数値を扱うため
1.0.0.9 → 1.0.0.10となり、1.0.0.aと 4 を超えるセグメントは拒否します)。同じヘルパーはexport-solutionのフェーズ 4、ステップ 4.0 からも呼び出されます。両方のスキルで、同じ入力バージョンには同一の更新結果を適用する必要があります。node "${PLUGIN_ROOT}/scripts/lib/bump-solution-version.js" \ --envUrl "{envUrl}" \ --token "{token}" \ --solutionId "{solutionId}" \ --projectRoot "."出力を JSON として取得してください。ヘルパーは
{ previous, next, bumped: true, manifestUpdated, manifestUpdateReason }を返します。--projectRoot "."を渡すと、ヘルパーが.solution-manifest.jsonのsolution.version(単一ソリューション)または一致するsolutions[].version(複数ソリューション)フィールドを自動的に更新します。これを指定しないと、更新のたびにマニフェストのバージョンが実際より古くなります。ローカルのexistingSolution.solution.versionを.nextに更新し、最終的なマニフェスト書き込みに更新後の値が反映されるようにしてください。マニフェストの一貫性をスキルが途中で中断された場合にも保てるよう、ステップ 5.6 のコンポーネント追加より前に実行してください。PATCH を直接記述しないでください。このスキルとexport-solutionでルールが食い違うことこそ、ヘルパーが防ぐために作られた種類のバグです。
正確なリクエスト本文のテンプレートについては、${PLUGIN_ROOT}/references/solution-api-patterns.md を参照してください。
-
パブリッシャーを作成する(まだ存在しない場合):
- パブリッシャー本文を指定して
POST {envUrl}/api/data/v9.2/publishersを実行します - レスポンスヘッダー
OData-EntityIdからpublisherIdを抽出します - 失敗した場合:エラーを報告して停止します(ソリューションの作成には進みません)
- このステップは、ソリューションの作成より前に必ず完了させてください。すべてのソリューション本文で
[email protected]をバインドします。直列で実行するステップであり、並列化はできません。
- パブリッシャー本文を指定して
-
ソリューションを作成する:
単一ソリューションモード(
MULTI_SOLUTION_MODE = false) —create-solution.jsを呼び出します。ヘルパーが呼び出し時にgetAuthToken(envUrl)を使ってトークンを更新するよう、--tokenは省略してください(古くなっている可能性のあるキャッシュ済みトークンを渡すと、ヘルパーが再試行しない 401 エラーが発生します)。node "${PLUGIN_ROOT}/scripts/lib/create-solution.js" \ --envUrl "{envUrl}" \ --uniqueName "{solutionUniqueName}" \ --friendlyName "{solutionFriendlyName}" \ --version "{version}" \ --publisherId "{publisherId}" \ --description "Power Pages solution for {siteName}"出力を JSON として取得し、
.solutionIdを抽出してsolutionIdに保存します。失敗した場合(終了コードが 0 以外、またはcreated: false):エラーを報告して停止します。複数ソリューションモード(
MULTI_SOLUTION_MODE = true) —create-solutions-batch.jsを呼び出します。このスクリプトは、Promise.allSettledを使ってすべてのPROPOSED_SOLUTIONSを並列に処理します(通常、ソリューションを 5~6 個に分割した場合、直列処理の約 10 秒に対し約 2 秒で完了します)。ヘルパーはisFutureBuffer: trueのエントリを自動的にスキップします(予約済みのバッファーは、ユーザーが実際に新しいコンポーネントを追加したときに作成されます)。また、verify-solution-exists.jsを使って 409 の競合を冪等に処理します。仕様を一時 JSON ファイルに書き込み、次を実行します。node -e "require('fs').writeFileSync('./docs/alm/.solutions-batch.json', JSON.stringify({{PROPOSED_SOLUTIONS_AS_SPECS}}))" node "${PLUGIN_ROOT}/scripts/lib/create-solutions-batch.js" \ --envUrl "{envUrl}" \ --token "{token}" \ --publisherId "{publisherId}" \ --solutionsFile ./docs/alm/.solutions-batch.jsonここで、
{{PROPOSED_SOLUTIONS_AS_SPECS}}はPROPOSED_SOLUTIONSの各エントリを{ uniqueName, friendlyName: displayName, version: "1.0.0.0", description, isFutureBuffer }にマッピングしたものです(ヘルパーがスキップできるよう、isFutureBufferフラグも引き継いでください)。出力を JSON として取得し、result.resultsからSOLUTIONS_BY_NAME = { uniqueName → { solutionId, created } }を構築します(skipped: trueのエントリは追加しません。Futureバッファーソリューションは、まだ Dataverse に存在しません)。result.failed > 0の場合は、各エントリのerror文字列を提示して停止します。作成に成功したソリューションは Dataverse に残るため、ユーザーは sync モードでsetup-solutionを再実行して復旧できます。一時ファイル./docs/alm/.solutions-batch.jsonは呼び出し後に削除します。バッチ処理の前に、トークンを最新の状態にする必要があります。
--tokenを渡さない場合、create-solutions-batch.jsは開始時にgetAuthToken(envUrl)を使って一度だけトークンを更新するため、古いトークンを渡すより--tokenを省略してください。 -
報告: 「Publisher
{name}の準備ができました。{N}個のソリューションを作成しました:{name1}、{name2}、…」(単一ソリューションモード: 1 つだけを報告します)。
フェーズ 5 — サイトコンポーネントの追加
AddSolutionComponent の本文テンプレートと powerpagecomponents の検出パターンについては、${PLUGIN_ROOT}/references/solution-api-patterns.md を参照してください。
同期モードの動作:
syncMode = trueの場合、--solutionIdを設定して検出ヘルパーを実行し、返されたmissing.*配列を候補セットとして使用します。このフェーズの他のすべて (5.1 の動的なコンポーネントタイプのルックアップ、5.3 の分類、5.4 の OAuth シークレット変換、5.4b の環境変数の採用、5.4c の孤立した ppc の採用、5.5 のマニフェストサマリー、5.6 の一括追加) は同じように実行されますが、「まだソリューションに含まれていないものだけ」という絞り込み済みのリストを使用する点だけが異なります。同期モードの目的は、setup-solutionの前回実行 後 にサーバーロジック、ボット、フロー、環境変数、またはページを追加したユーザーが、スキルを再実行するだけで、新規セットアップのプロンプトなしにそれらのコンポーネントを採用できるようにすることです。新規モードの動作 (
syncMode = false): 以下に記載する完全な検出を実行します — すべての ppc、すべてのサイト言語、すべてのカスタムテーブル、すべての publisher プレフィックスの環境変数が採用の候補になります。
ステップ 5.1 — コンポーネントタイプを動的に検出する
コンポーネントタイプの番号をハードコードしないでください。 コンポーネントタイプのコードは環境固有のメタデータであり、テナントごとに異なります。常に discover-component-types.js を使用して実行時に解決してください。
discover-component-types.js を、ウェブサイトレコード ID と、1 つのサンプル powerpagecomponent ID および 1 つのサイト言語 ID (以下のステップ 5.2 の事前検出クエリから取得 — まだ取得できていない場合は先にそちらを実行してください) を指定して実行します:
node "${PLUGIN_ROOT}/scripts/lib/discover-component-types.js" \
--envUrl "{envUrl}" \
--token "{token}" \
--websiteRecordId "{websiteRecordId}" \
--powerpageComponentId "{anyPowerpageComponentId}" \
--siteLanguageId "{siteLanguageId}"
出力を JSON としてキャプチャし、.websiteComponentType、.subComponentType、.siteLanguageComponentType を抽出します。ヘルパーが返した JSON の値をそのまま正確に使用してください — ドキュメントにある「典型的な」値を代用しないでください。 テナント間で観測された参考値には 10426/10427/10428 や 10429/10428/10430 がありますが、実際の値は環境ごとに異なるため、このスクリプトの実行時クエリから取得する必要があります。3 つの兄弟統合エンティティはそれぞれ独自の componenttype を持ちます — サイト言語はウェブサイトの AddRequiredComponents: true には含まれず、明示的に追加する必要があります。3 エンティティモデルの全体像については references/solution-api-patterns.md を参照してください。
スクリプトがウェブサイトレコードがまだどのソリューションにも含まれていないと報告した場合は、停止して、サイトをソリューション化する前にデプロイする必要がある (/power-pages:deploy-site を使用) ことをユーザーに伝えてください。subComponentType が存在しない場合 (まだサブコンポーネントがインデックス化されていない)、そのまま続行してください — すべてのコンポーネント ID はステップ 5.2 で検出します。
ステップ 5.2 — すべてのコンポーネントを検出する
6 つの検出クエリを並列で実行します:
A. コンポーネントタイプのラベル (表示名用):
GET {envUrl}/api/data/v9.2/GlobalOptionSetDefinitions(Name='powerpagecomponenttype')
typeLabel マップを構築します: { [Value]: Label.UserLocalizedLabel.Label }。これが失敗した場合は、${PLUGIN_ROOT}/references/solution-api-patterns.md のセクション 3b の静的テーブルにフォールバックします。
B. このサイトのすべての Power Pages サブコンポーネント:
GET {envUrl}/api/data/v9.2/powerpagecomponents
?$filter=_powerpagesiteid_value eq '{websiteRecordId}'
&$select=powerpagecomponentid,name,powerpagecomponenttype
&$orderby=powerpagecomponenttype
@odata.nextLink のページネーションに従います。typeLabel を表示名として使用し、powerpagecomponenttype でグループ化します。
C. サイト言語レコード:
GET {envUrl}/api/data/v9.2/powerpagesitelanguages?$filter=_powerpagesiteid_value eq '{websiteRecordId}'&$select=powerpagesitelanguageid,languagecode,displayname
すべての言語 ID を保存します。
D. Dataverse テーブル — publisher プレフィックスを共有するすべてのテーブルではなく、サイトが実際に参照しているテーブルを検出します。
publisher プレフィックスを使用しない理由: プレフィックスのマッチングは、共有/デフォルトの publisher (
new_、環境のデフォルト) の場合に壊滅的に過剰カウントします — 6 テーブルのサイトが 22 個の無関係なテーブルにマッチすることがあります — また、サイトの実際のテーブルが異なるプレフィックス (たとえばedmpublisher 配下のbp_*テンプレート) に由来する場合にはそれらを見逃します。権威あるシグナル (SME 確認済み) は、サイトのテーブル権限です: 「サイトでテーブルが使用されている場合、そのテーブルに対する権限が存在します。」 サイトのテーブル権限とデータモデルのマニフェストを、環境のカスタム非管理テーブルと照合してスコープを設定する共通の検出ヘルパーを実行します。
node "${PLUGIN_ROOT}/scripts/lib/discover-site-components.js" \
--envUrl "{envUrl}" --token "{token}" --siteId "{websiteRecordId}" \
--projectRoot "." \
{if .datamodel-manifest.json elsewhere: --datamodelManifest "<path>"}
返された customTables[](各要素は { id, logicalName, schemaName, displayName }。id は AddSolutionComponent 呼び出しに使用する MetadataId)を使用します。これはすでにサイトのスコープに合わせた正しい一覧です。プレフィックスで再フィルタリングしないでください。
テーブルに関する重要な注意:Dataverse ソリューションに含まれるのはスキーマのみです。つまり、エンティティ定義、列、リレーションシップ、フォーム、ビューです。テーブルのデータ/レコードはソリューションに含まれません。ターゲット環境に初期データや参照データが必要な場合は、別途データ移行の手順が必要です。
E. Cloud Flow のリンクコンポーネント(powerpagecomponenttype 33)— 実行時のフィールドのイントロスペクション:
このサイトを Cloud Flow にリンクする powerpagecomponent レコードをクエリします。
GET {envUrl}/api/data/v9.2/powerpagecomponents
?$filter=_powerpagesiteid_value eq '{websiteRecordId}' and powerpagecomponenttype eq 33
&$select=powerpagecomponentid,name
結果が返された場合は、最初のレコードを $select なしで取得し、ワークフローのルックアップフィールドを特定します。
GET {envUrl}/api/data/v9.2/powerpagecomponents({firstComponentId})
応答 JSON から、websiteRecordId と一致しない、GUID が null 以外の _*_value キーを探します。残ったキーがワークフローのルックアップフィールドです(例:_adx_workflow_value)。そのフィールドを $select に指定して、タイプ 33 のすべてのコンポーネントを再クエリし、すべての関連する workflowId GUID を収集します。次に、各ワークフローの名前と状態を解決します。
GET {envUrl}/api/data/v9.2/workflows({workflowId})?$select=name,workflowid,statecode
AddSolutionComponent 用に、ワークフローのコンポーネントタイプも検出します。
GET {envUrl}/api/data/v9.2/solutioncomponents?$filter=objectid eq '{workflowId}'&$select=componenttype&$top=1
workflowComponentType として保存します。クエリ結果が空(フローがまだどのソリューションにも含まれていない)の場合は、その旨を記録します。関連するフローレコード自体は存在しており、追加できます。
タイプ 33 のクエリでレコードが返されない場合は、cloudFlows = [] として保存し、スキップします。
F. Bot Consumer のリンクコンポーネント(powerpagecomponenttype 27)— 実行時のフィールドのイントロスペクション:
Query E と同じ手順です。タイプ 27 の powerpagecomponent レコードをクエリし、最初のレコードをイントロスペクションしてボットのルックアップフィールドを特定し、ボット GUID を収集し、次のクエリでボット名を解決します。
GET {envUrl}/api/data/v9.2/bots({botId})?$select=name,botid,statecode
また、solutioncomponents を使用してボットのコンポーネントタイプを検出します。botComponents として保存します。タイプ 27 のレコードが存在しない場合は、botComponents = [] として保存し、スキップします。
G. このソリューション内のクラウドフローが使用する接続参照:
クラウドフローは、connectionreference レコードを介してコネクタを参照します。これらのレコードは独立した Dataverse エンティティです。ソリューションに含まれていない場合、ソリューションのエクスポートは問題なく完了しますが、ターゲット環境へのインポート時に MissingDependency/接続参照の検証エラーで失敗します。ここで列挙し、Step 5.6 で追加する必要があります。
Query E の結果が cloudFlows = [] の場合は、このクエリをスキップします。
-
このサイトのパブリッシャーが所有する接続参照をクエリします。
GET {envUrl}/api/data/v9.2/connectionreferences ?$filter=startswith(connectionreferencelogicalname,'{publisherPrefix}_') &$select=connectionreferenceid,connectionreferencelogicalname,connectionreferencedisplayname,connectorid -
各クラウドフローについて(Query E から)、
clientdataJSON(workflows({workflowId})?$select=clientdata)を解析し、使用しているconnectionReferenceLogicalNames を特定します。Query G.1 の結果を該当する参照だけに絞り込みます。これらはソリューションに含める必要がある参照です。 -
接続参照の componenttype を実行時に解決します。値は環境によって異なります(テナント間で
10137や10160が確認されています。ハードコードしないでください)。node "${PLUGIN_ROOT}/scripts/lib/discover-component-types.js" \ --envUrl "{envUrl}" --token "{token}" \ --websiteRecordId "{websiteRecordId}" \ --objectIds "{firstConnectionReferenceId}"
.resolved[0].componentType を読み取り、connectionReferenceComponentType として保存します。接続参照がまだどのソリューションにも含まれていない場合(resolved[0].componentType === null)、まだ追加されていません。値が解決されるまで、呼び出しごとに ID を 1 つ渡すか、既知の同種の接続参照をクエリします。実行時に解決した値がない場合は、推測しないでください。
絞り込んだ一覧を connectionReferences[] として保存します。
Query G で参照が返されない場合(クラウドフローがコネクタを使用していない場合、またはパブリッシャーのプレフィックスが一致しない場合。これはまれです)は、connectionReferences = [] として保存し、スキップします。クラウドフローが存在するのに一致する接続参照が見つからない場合は、ソフト警告を表示します。ユーザーは、ターゲット環境でバインドが必要なコネクタをフローが使用しているか確認してください。
ステップ 5.3 — サイト設定を分類する
preloadedSettings が利用可能な場合(フェーズ 1 のステップ 5 でユーザーが「プランから事前ロードされた選択肢を使用」を選択した場合)、以下の分類をスキップし、preloadedSettings.keepAsIs、preloadedSettings.promoteToEnvVar、preloadedSettings.authNoValue、preloadedSettings.credentialNeedsDecision を直接使用します。(2026-05-08 より前に生成されたプランでは、古い excluded バケットが使われています。後方互換性のため、その内容は credentialNeedsDecision として扱います。)
それ以外の場合は、共有分類器 ${PLUGIN_ROOT}/scripts/lib/classify-site-settings.js を実行します。これは、plan-alm フェーズ 1 ステップ 7 と共有される認証情報の正規表現とティアマッピングの唯一の信頼できる情報源です。CLI を呼び出す方法(JSON を標準入力にパイプ)またはインラインで require() する方法のどちらでも実行できます。出力は plan-alm が生成するものと同じ 4 つのバケット構成です。
{
keepAsIs: [{name}], // Tier 3 — added to the solution unchanged
authNoValue: [{name}], // Tier 2b — Authentication/AzureAD with empty value; added as-is, user sets per-env
promoteToEnvVar: [{name, value}], // Tier 2a — Authentication/AzureAD with value; reviewed at Step 5.4.A
credentialNeedsDecision: [{name, value}] // Tier 1 — credential-style names; bulk-with-override prompt at Step 5.4.C
}
ティアの定義(classify-site-settings.js の正規表現に対応):
| ティア | バケット | マッチ条件 | 処理 |
|---|---|---|---|
| 1 — 認証情報形式 | credentialNeedsDecision | CREDENTIAL_REGEX(ConsumerKey|ConsumerSecret|ClientId|ClientSecret|AppSecret|AppKey|ApiKey|Password、大文字と小文字を区別しない) | ステップ 5.4.C で一括処理・上書きプロンプトを表示 — 自動分類(Secret/String を既定値として使用)、すべて Secret、すべて String、すべてスキップ、または認証情報ごとに選択 |
| 2a — 値が設定された認証設定 | promoteToEnvVar | AUTH_PREFIX_REGEX(Authentication/ または AzureAD/)に一致し、認証情報ではなく、値が設定されている | ステップ 5.4.A で複数選択プロンプトを表示 — 環境変数に紐付ける設定を選択 |
| 2b — 値が設定されていない認証設定 | authNoValue | 同じプレフィックスで、値が設定されていない | 注記付きでそのままソリューションに追加(値は環境ごとにユーザーが設定) |
| 3 — その他すべての設定 | keepAsIs | 上記以外すべて | 変更せずにソリューションに含める |
ここに正規表現をインラインで記述しないでください。このスキルでは誤っていても plan-alm では正しい場合、プラン作成時と実行時で分類にずれが生じます。正規表現は classify-site-settings.js のみに定義されています。両方のスキルでこれを読み込んでください。
authNoValue 設定について:これらは、開発環境で値が設定されていない認証設定です。そのままソリューションに追加されます。各ターゲット環境へのデプロイ後に、そこで正しい値を設定する必要があります。マニフェストのレビュー時(ステップ 5.5)に、警告の注記ボックス内にこれらを表示してください。
ステップ 5.4 — 認証設定を処理する:環境変数に昇格しますか?
最終マニフェストを提示する前に、keepAsIs 以外の 3 つのカテゴリーを処理します。
A. promoteToEnvVar 設定(値が設定された認証設定):
🚦 ゲート(plan · setup-solution:5.4a.promote): 認証設定を複数選択し、環境変数に昇格するものを選択します。選ばれなかった設定は、通常のサイト設定のままにします。
AskUserQuestion を multiSelect: true で呼び出し、各 promoteToEnvVar 設定の名前と現在の値を一覧表示します。
「これらの認証設定には、開発環境で値が設定されています。環境ごとに異なる値にする設定(例:機能フラグ、ログインモード、AzureAD テナント設定)がある場合は、環境変数に昇格してください。ソリューションで管理され、デプロイ時に各ステージの値が適用されます。その他は通常のサイト設定のままにしてください。」
- 設定ごとに 1 つの選択肢(例:
Authentication/Registration/LocalLoginEnabled = true) - 追加の選択肢:「すべて環境変数に昇格する」および「すべて通常のサイト設定のままにする」
ユーザーが環境変数への昇格を選択した各設定について:
${PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.jsを使って、正規のスキーマ名を生成します。これにより、後続のconfigure-env-variablesとdeploy-pipelineが想定する名前と一致します。
出力:node "${PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.js" \ --publisherPrefix "{prefix}" \ --settingName "{settingName}"{ schemaName, sanitized }。このヘルパーは正規ルール({prefix}_{sanitized(settingName)}.toLowerCase())の唯一の信頼できる情報源です。ルールをインラインで記述しないでください。同じ論理設定に対して、setup-solution と configure-env-variables が同一のスキーマ名を出力しなければなりません。ルールをインラインで記述すると、出力が一致しなくなるおそれがあります。- 解決されたスキーマ名を使用して
environmentvariabledefinitionを作成します:
認証設定の構成には型node "${PLUGIN_ROOT}/scripts/lib/create-env-var-definition.js" \ --envUrl "{envUrl}" \ --token "{token}" \ --schemaName "{schemaName from step 1}" \ --displayName "{friendlyName}" \ --type 100000000100000000(String)を使用します(Secret ではありません — これらは機能フラグであり、資格情報ではないためです)。出力を JSON として取得し、.definitionIdと.schemaNameを抽出します。 - コンポーネントリストに含めるために
definitionIdを記録します(ステップ 5.6、ComponentType: 380)。 link-site-setting-to-env-var.jsを使用してサイト設定を環境変数にリンクします:node "${PLUGIN_ROOT}/scripts/lib/link-site-setting-to-env-var.js" \ --envUrl "{envUrl}" \ --token "{token}" \ --siteSettingId "{settingId}" \ --definitionId "{definitionId}" \ --schemaName "{schemaName}".okと.verifiedが両方ともtrueであることを確認します。
ユーザーが昇格しないことを選択した設定は、promoteToEnvVar から keepAsIs に移動します — これらはプレーンなサイト設定としてソリューションに含まれます。
B. authNoValue 設定(認証構成、開発値なし):
ユーザーの判断は不要です。これらはそのまま自動的にソリューションに含まれます。ステップ 5.5 で、警告ボックスに表示します:
「以下の認証設定には、開発環境で値が設定されていません。これらはそのままソリューションに追加されます。各ターゲット環境にデプロイした後、そこで正しい値を確認または設定してください。」
C. credentialNeedsDecision 設定(資格情報スタイル — 一括と上書きのプロンプト):
これらはソリューションに含める前に判断が必要な資格情報スタイルのサイト設定(ConsumerKey / ClientSecret など)です。ソリューション zip 内に生の値をそのまま入れて出荷することは実際の露出リスクとなるため、安全な方法はサイト設定レコードをソリューションに追加し、ステージごとに環境変数を介して値をルーティングすることです。N が大きい場合、資格情報ごとに尋ねるのは負担が大きすぎます(一般的なサイトには複数の OAuth プロバイダーにまたがる 20 以上の認証関連資格情報があります)ので、このステップでは一括と上書きのプロンプトを使用します:1 つの質問で N 個すべての資格情報をカバーし、きめ細かい制御のための資格情報ごとのエスケープハッチを備えています。
ステップ 5.4.C.1 — 名前パターンによる自動分類。
各設定に対して ${PLUGIN_ROOT}/scripts/lib/classify-site-settings.js から autoClassifyCredential(name) を呼び出します。このヘルパーは以下の正規表現を順番に適用します(唯一の信頼できる情報源 — ここで重複させないでください):
| デフォルト | ヘルパー内のマッチャー | 適用される条件 |
|---|---|---|
Secret 環境変数(type: 100000005) | CREDENTIAL_SECRET_REGEX(Secret|Password|ApiKey|AppKey) | これらの部分文字列を含む名前 — *ClientSecret、*AppSecret、*Password、*ApiKey、*AppKey |
String 環境変数(type: 100000000) | CREDENTIAL_STRING_REGEX(Id|ConsumerKey)かつ Secret でない | *ClientId、*ConsumerKey、*TenantId、*AppId などの名前 |
| Secret 環境変数(フォールバック) | (どちらにも一致しない場合のヘルパーの防御的デフォルト) | その他すべて — 防御的:資格情報名はデフォルトで機密扱い |
ヘルパーは各設定に対して { default: 'secret' | 'string', reason } を返します。結果を AUTO_CLASSIFY = { secrets: [...], strings: [...] } にグループ化し、ユーザーに 1 行のサマリーを表示します:「{N} 個の資格情報スタイル設定を自動分類しました:{S} 個を Secret 環境変数(ステージごとの Key Vault)、{T} 個を String 環境変数(ステージごとのプレーンテキスト)として。」
ステップ 5.4.C.2 — 一括プロンプト。
🚦 ゲート(同意 · setup-solution:5.4c.credentials): 資格情報の一括処理の決定 — Secret 環境変数(ステージごとの Key Vault)、String 環境変数(ステージごとのプレーン)、またはスキップ。資格情報ごとの選択。シークレット値がソリューション zip に入って出荷されるかどうかを決定します。
N 個すべての資格情報をカバーする1 つの AskUserQuestion を尋ねます:
「{N} 個の資格情報スタイルのサイト設定を検出しました(
{firstFew.join(', ')}{N>3?', ...':''})。どのように処理しますか?これらの値をソリューション zip 内に入れて出荷することは実際の露出リスクとなるため、推奨される方法はサイト設定レコードをソリューションに追加し、ステージごとに環境変数を介して値をルーティングすることです。実際のシークレット値が zip に入って出荷されることはありません — 環境ごとに
deploymentsettingsjsonで設定されます。」 Options:
- 名前による自動分類 (推奨) — Step 5.4.C.1 の自動分類を適用します。{S} は Secret 環境変数、{T} は String 環境変数として扱います。確認は 1 回で、{N} 件すべてを処理します。(デフォルトのオプションです。)
- すべて Secret 環境変数 — すべての認証情報を、Key Vault に保存する Secret 環境変数として扱います。保守的な方法で、どの認証情報にも使えますが、Key Vault が実際には不要なステージ値にも依存関係が追加されます。
- すべて String 環境変数 — すべての認証情報を、ステージごとのプレーンテキスト環境変数として扱います。どの認証情報も本当の秘密ではない場合(例:内部限定のテスト環境)にのみ使用してください。
- すべてスキップ — ソリューションには何も追加しません。ユーザーが各環境の認証情報をすべて個別に管理します。IronItOut 導入前の「除外」動作と同等です。
- 認証情報ごとに選択 — 詳細に制御できるよう、認証情報ごとにプロンプトを表示します(設定ごとに Secret/String/スキップを選択)。自動分類では適切に分類できない、本当の秘密と機密性のない ID が混在している場合に使用してください。
分岐ロジック:
- オプション 1(自動分類):
AUTO_CLASSIFY.secretsの各設定について、以下の環境変数作成手順を--type 100000005で実行します。AUTO_CLASSIFY.stringsの各設定については、--type 100000000で実行します。追加のプロンプトは表示しません。 - オプション 2(すべて Secret):N 件すべてを Secret として扱います。同じループを
--type 100000005で実行します。 - オプション 3(すべて String):N 件すべてを String として扱います。同じループを
--type 100000000で実行します。 - オプション 4(すべてスキップ):N 件すべてを
userOptedOutOfSolutionバケットに移します。Step 5.5 で次のように表示します:"次の認証情報形式の設定はユーザーの要望によりスキップされており、ソリューションには含まれていません。デプロイ後、各対象環境で手動設定してください:{names}。" - オプション 5(認証情報ごとに選択):各設定について、3 つの選択肢(Secret 環境変数/String 環境変数/スキップ)を含む
AskUserQuestionを実行します。自動分類の結果を各プロンプトのデフォルトに反映しますが、ユーザーは変更できます。
Step 5.4.C.3 — 環境変数の作成(オプション 1、2、3、およびオプション 5 でスキップ以外を選択した場合に共通)。
環境変数を使用して処理する各設定について:
-
${PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.jsを使って正規のスキーマ名を生成します(Step 5.4.A と同じヘルパーです。単一の信頼できる情報源を使うことで、後から configure-env-variables と deploy-pipeline が同じスキーマ名を参照できます):node "${PLUGIN_ROOT}/scripts/lib/generate-env-var-schema-name.js" \ --publisherPrefix "{prefix}" \ --settingName "{settingName}" -
解決したスキーマ名を使って
environmentvariabledefinitionを作成します:node "${PLUGIN_ROOT}/scripts/lib/create-env-var-definition.js" \ --envUrl "{envUrl}" \ --token "{token}" \ --schemaName "{schemaName from step 1}" \ --displayName "{friendlyName}" \ --type "{100000005 for Secret, 100000000 for String}"Secret 環境変数の場合は、
--defaultValueを渡さないでください。開発値は定義ではなく、ステージごとに Key Vault に保存します。String 環境変数の場合は、開発値をデフォルト値として取得します。 -
コンポーネント一覧に含める
definitionIdを記録します(Step 5.6、ComponentType: 380)。 -
link-site-setting-to-env-var.jsを使ってサイト設定を環境変数にリンクします(上記の Step 5.4.A と同じ呼び出しです)。 -
サイト設定自体も環境変数定義と一緒にソリューションに追加します。どちらも追跡対象のコンポーネントです。
環境変数の作成が 1 件でも失敗した場合(ループ中のトークン有効期限切れ、スキーマ名の衝突など)は、設定名と理由を示して失敗を通知し、その設定だけを再試行するか、スキップするか、または一括処理全体を中止するかをユーザーに尋ねます。認証情報を黙って破棄してはいけません。
後方互換性:2026-05-08 より前に生成された preloadedSettings プランを読み込む場合は、preloadedSettings.excluded の各エントリを credentialNeedsDecision として扱い、上記の一括処理と上書きのプロンプトを実行してください。
ステップ 5.4b — 孤立した環境変数定義を取り込む
上記の OAuth シークレット変換とは別に、他のスキル(特に setup-auth、add-server-logic、configure-env-variables)が以前作成したものの、ユーザーソリューションに追加されていない環境変数定義がある場合があります。これらは Default ソリューションに残り、気づかないうちに差異が生じます。このステップでは、これらを検出して取り込みます。
共有の検出ヘルパーを実行し、1 回の呼び出しでサイトの完全なインベントリを取得します。
node "${PLUGIN_ROOT}/scripts/lib/discover-site-components.js" \
--envUrl "{envUrl}" --token "{token}" \
--siteId "{websiteRecordId}" \
--publisherPrefix "{publisherPrefix}" \
--solutionId "{solutionId}"
標準出力を JSON として解析し、missing.envVars を読み取ります。これは、schemaname が発行者プレフィックスで始まっているものの、このソリューションの solutioncomponents にはまだ含まれていない環境変数定義です。
各エントリについて、ユーザーが Default のみの孤立した定義と、別のユーザーソリューションが意図的に所有している環境変数とを区別できるよう、現在どのソリューションに属しているかも照会します。
GET {envUrl}/api/data/v9.2/solutioncomponents
?$filter=objectid eq {definitionId}&$select=_solutionid_value
次に、該当するものごとにソリューションの uniquename を取得します。各環境変数に以下のタグを付けます。
DEFAULT-ONLY—Defaultソリューションのみが所有しています(他のスキルによって作成された、典型的な孤立した定義です)。IN OTHER SOLUTION: <uniquename>— ユーザーソリューションが所有しています。ユーザーが意図的にそのソリューションに含めている可能性があります。
DEFAULT-ONLY タグの付いた環境変数が 1 つ以上ある場合は、multiSelect: true を指定して AskUserQuestion で確認します。
🚦 ゲート(plan · setup-solution:5.4b.orphan-envvars): 発行者プレフィックスに一致し、まだこのソリューションに含まれていない環境変数定義を取り込みます。
DEFAULT-ONLYの孤立した定義は「推奨」としてあらかじめ選択されています。別のユーザーソリューションがすでに所有している環境変数も一覧表示しますが、あらかじめ選択されません(所有先を変更する意図がある場合にのみ、ユーザーが選択します)。
「発行者プレフィックス(
{prefix}_)が付いた環境変数定義のうち、まだ {solutionUniqueName} に含まれていないものが見つかりました。含めるものを選択してください。取り込まれるのは定義のみです。値は環境ごとに保持され、移行されません。
{schemaName}({displayName}) — タイプ {type}、現在の所属先:{tag}- ...
さらに:DEFAULT-ONLY の孤立した定義をすべて含める(推奨) / 今回はスキップ」
選択されたエントリを adoptedEnvVars: [{ definitionId, schemaName, displayName, type }] に格納します。
何も選択されない場合、またはリストが空の場合、adoptedEnvVars は空のままにし、スキルは何も通知せずに続行します。
このステップがある理由:この確認を行うまでは、他のスキルが作成した環境変数はサイトのソリューションから気づかれないまま除外され、ステージング環境や本番環境に移行されませんでした。ここでこれらを表示することは、
AGENTS.mdの ALM をデフォルトで考慮する原則が求める、スキル間のセーフティネットです。
ステップ 5.4c — 孤立した Power Pages コンポーネントを取り込む
5.4b と同様の処理を、powerpagecomponent 行に対して行います。サイト上にあり、他のスキルや pac pages upload-code-site によって作成されたものの、ユーザーソリューションに追加されていないコンポーネントを検出します。2026-04-22 の実環境での検証で確認された典型例は次のとおりです。
invoice-checkerサーバーロジック(タイプ 35) — 以前のセッションで/power-pages:add-server-logicによって追加されましたが、ユーザーソリューションには登録されていません。index.html(タイプ 3) —pac pages upload-code-siteによって更新された、現在の SPA エントリページです。再ビルドのたびに新しいindex.htmlレコードが作成されますが、ユーザーソリューションには自動で追加されません。
共有の検出ヘルパーを使って孤立した項目のリストを取得します(Vite/Rollup のバンドルチャンクである Home-XYZ.js、index-XYZ.css などはすでに除外されているため、ハッシュ名の項目がプロンプトに大量に表示されることはありません)。
node "${PLUGIN_ROOT}/scripts/lib/discover-site-components.js" \
--envUrl "{envUrl}" --token "{token}" \
--siteId "{websiteRecordId}" \
--publisherPrefix "{publisherPrefix}" \
--solutionId "{solutionId}"
JSON 出力から missing.powerpagecomponents を取得し、次のように分類します。
- 実コンテンツの孤立した項目 —
nameがバンドルチャンクの正規表現([-.][A-Za-z0-9_-]{7,14}\.(js|mjs|cjs|css)(\.map)?$)に一致しないエントリです。これらを取り込みます。 - バンドルチャンクの孤立した項目 — サマリー用に件数を保持しますが、取り込みの確認は行いません。これらは実コンテンツではなく、古いビルド成果物です。別途整理することを提案し、フェーズ 7 のサマリーで報告します。
実コンテンツの孤立レコードごとに、
nameでも重複を排除します。index.htmlの行が複数あり、そのうち1つがすでにソリューション内にある場合(modifiedonがより新しいもの)、古い孤立レコードは古い重複です。採用プロンプトから除外し、古い重複としてログに記録します。ルール:(powerpagecomponenttype、name)のペアごとに、最も新しい孤立レコードのみを残します。
missing.siteLanguages も対象にしてください。これらは、サイトには存在するもののユーザーのソリューションには含まれていない powerpagesitelanguage レコード(コンポーネントタイプ 10428)です。これらは省略できません。言語レコードがないサイトをインポートすると、powerpagesite.content.defaultlanguage がターゲット環境に存在しない ID を参照するため、認証後のレンダリングがエラーを表示せずに失敗します。孤立レコードの採用プロンプトには各エントリをそのまま含めてください。言語には除外対象となるバンドルチャンクのノイズがないため、すべて事前選択して推奨項目にします。
実コンテンツの孤立レコードリストまたは missing.siteLanguages が空でない場合は、multiSelect: true を指定して AskUserQuestion でプロンプトを表示します。
🚦 ゲート(plan · setup-solution:5.4c.orphan-ppcs): サイトには存在するものの、このソリューションには含まれていない孤立した
powerpagecomponent行(powerpagesitelanguageレコードを含む)を採用します。サイトの言語は、省略すると認証後のレンダリングが通知なしに失敗するため、「推奨」として事前選択します。その他の ppc 孤立レコード(例:invoice-checkerのサーバーロジック)は、実コンテンツとみられる場合に事前選択します。古いビルドアーティファクトのバンドルチャンクは上流で除外されます。
{N} 個のサイトコンポーネントが {solutionUniqueName} にまだ含まれていません:
{name}(タイプ {type} {typeLabel})— 現在の所属先:{currentSolution}- ...
さらに:すべての孤立レコードを含める(推奨)/今回はスキップ
選択項目を adoptedPpcs: [{ id, name, type, typeLabel }] に格納します。
ユーザーが選択した場合は、各エントリに対して AddRequiredComponents: false と正しい ComponentType を指定して AddSolutionComponent を呼び出します(Step 5.1 で discover-component-types.js により解決された値を使用し、ハードコードしないでください)。
missing.powerpagecomponentsのエントリにはsubComponentTypemissing.siteLanguagesのエントリにはsiteLanguageComponentType
DoNotIncludeSubcomponents: true は設定しないでください。Dataverse API は非 Entity ルートコンポーネントに対してこのフラグを拒否し(HTTP 400 0x80040216)、これらの統合エンティティ行には不要です。
フィルタリング後の missing.powerpagecomponents と missing.siteLanguages がどちらも空の場合、このステップは何も表示せずに実行されます。
このステップが必要な理由:このチェックが導入される前は、
setup-solutionが完了してユーザーがすべて完了したと思っている一方で、invoice-checker/index.htmlなどのサイト関連レコードがひそかにActiveソリューションに残り、ステージング環境や本番環境に移行されない、という問題が繰り返し発生していました。今日の本番環境での検証では、SupplierInvoicePortal に実際の孤立レコード(invoice-checker)が1つ見つかり、AddSolutionComponentで採用しました。ソリューションのバージョンは v1.0.0.1 から v1.0.0.2 に上がりました。
ステップ 5.5 — マニフェスト全体を提示し、ユーザーの確認を得る
ここが重要な判断ポイントです。 追加するすべての項目を含むマニフェスト全体を作成し、書き込みを行う前にユーザーに提示します。
カスタムテーブルが見つかった場合(ステップ D で確認した、サイトが参照するテーブル群)、最終マニフェストを表示する前に、multiSelect: true を指定して AskUserQuestion で質問します。
- 最初の選択肢:「参照されているテーブル N 個すべてを含める(推奨)」 — デフォルトで選択済み。N はサイトが実際に参照しているテーブル数です(環境全体のプレフィックス一覧ではありません)。
- テーブルごとに選択肢を 1 つ:
{logicalName} ({DisplayName}) - 最後の選択肢:「テーブルをすべて除外する」
デフォルトのリストは、すでにサイトの実際のテーブルに絞り込まれています(ステップ D)。サイトに必要だが権限がまだない追加のテーブルをユーザーが把握している場合は、手動で追加できます。ただし、デフォルトに発行元プレフィックスの一覧全体を含めてはいけません。
構造化された概要として提示します。
Here is everything that will be added to solution "{solutionName}":
WEBSITE & LANGUAGE
✓ Website record: {siteName}
✓ Site language(s): English (en-US)
SITE COMPONENTS ({total} components across {K} types)
✓ Publishing States (2)
✓ Web Pages (10)
✓ Web Files (90) — compiled JS/CSS/HTML assets
✓ Page Templates (5)
✓ Web Templates (13)
✓ Content Snippets (11)
✓ Web Roles (2)
✓ Website Access (6)
✓ Table Permissions (13) — required for Web API authorization in target env
✓ Site Markers (5)
✓ Webpage Rules (2)
SITE SETTINGS (64 included)
✓ Web API settings (14): Webapi/crd50_invoice/enabled, ...
✓ Feature flags (32): CodeSite/Enabled, Search/Enabled, ...
✓ Auth config (18): Authentication/Registration/LocalLoginEnabled, ...
~ OAuth as env vars (3): ids_auth_openauth_microsoft_clientsecret, ... [ENV VAR]
✗ OAuth excluded (5): Authentication/OpenAuth/Facebook/AppSecret, ... [EXCLUDED]
CLOUD FLOWS ({N} linked via powerpagecomponent type 33)
✓ Invoice Approval Flow (workflowId: {guid}, Active)
~ Draft Flow (workflowId: {guid}, Inactive — excluded by default)
BOT CONSUMERS ({N} linked via powerpagecomponent type 27)
✓ Support Bot (botId: {guid}, Active)
DATAVERSE TABLES (schema only — no data)
✓ crd50_invoice (Invoice)
...
ENV VAR DEFINITIONS (componenttype 380)
✓ ids_auth_openauth_microsoft_clientsecret (Secret) [converted from OAuth secret]
✓ crd50_auth_openauth_microsoft_clientsecret (Secret) [ADOPTED — was in Default only]
...
Total to add: ~{N} components
分かりやすくするため、マニフェスト内の各環境変数の項目の後に、次のタグを付けます。
[converted from OAuth secret]— ステップ 5.4 でサイト設定から作成[ADOPTED — was in Default only]— この実行より前から存在し、ステップ 5.4b でソリューションに取り込み[ADOPTED ppc — was in Active only]— ステップ 5.4c で powerpagecomponent を取り込み(例:invoice-checkerのサーバーロジック。実際のサイトページはまだ登録されていません)[ADOPTED — also in {otherSolutionName}]— 別のユーザーソリューションに存在し、ここにも追加(ユーザーが明示的に追加を選択)
cloudFlows が空でない場合、multiSelect: true を指定して AskUserQuestion で質問します。
- 選択肢:「アクティブなクラウドフロー N 個すべてを含める(推奨)」
- フローごとに選択肢を 1 つ:
{name} ({workflowId}) - 選択肢:「クラウドフローをすべて除外する」
デフォルトでは、アクティブなフローを含め、非アクティブなフローを除外します。フローがすでに別のソリューションに含まれている場合は、ユーザーに警告します。「このフローはソリューション X に含まれています。ここに追加すると、移動されます。」
botComponents が空でない場合、multiSelect: true を指定して AskUserQuestion で質問します(同じパターン)。
どちらも空の場合は質問をスキップし、(None discovered) と表示します。
マニフェストの概要を提示した後、自由入力の追加手段を設けます。
「このソリューションに含めるべきクラウドフローやボットが上記に表示されていない場合は、GUID を貼り付けてください(カンマ区切り)。続行する場合は空欄のままにしてください。」
🚦 ゲート(plan · setup-solution:5.5.manifest-confirm):
AddSolutionComponentによる書き込みの前に、最終マニフェストを確認します。テーブル、フロー、ボット、環境変数、孤立コンポーネントの取り込みが対象です。ここでキャンセルしても、インメモリのマニフェストは保持されますが、Dataverse への書き込みは行われません。
AskUserQuestion で次のように質問します。
「こちらでよろしいですか?この内容で進めるか、除外するカテゴリやテーブルを指定できます。」
選択肢:「この選択内容で進める」/「変更したい項目がある」
ステップ 5.6 に進む前に、明示的な確認を待ちます。
ステップ 5.6 — 確認済みのすべてのコンポーネントを追加する
追加するすべてのコンポーネントを含む JSON 配列を作成し、scripts/lib/add-components-to-solution.js を呼び出して、一括処理を実行します。トークンの更新と冪等性の処理は組み込みで行われます。
コンポーネント配列は次の順序で作成します。
- Web サイトレコード —
{ componentId: websiteRecordId, componentType: websiteComponentType, addRequired: true, description: "Website: {siteName}" } - サイト言語レコード — 各言語につき 1 エントリ。
siteLanguageComponentTypeを使用します(AddRequiredComponentsでは自動的に含まれません)。 - 確認済みのすべての powerpagecomponent グループ — コンポーネントごとに 1 エントリ。
subComponentTypeを使用します。- テーブル権限(タイプ 18)は標準の powerpagecomponent です。デフォルトで含めてください。
- 環境変数に変換されていない OAuth シークレットのサイト設定は除外してください。
- 環境変数定義 — 定義ごとに
{ componentType: 380 }を使用して 1 エントリを追加します。以下を含めてください。- Step 5.4(OAuth シークレットの変換)で作成したすべての環境変数
- Step 5.4b の
adoptedEnvVarsに含まれるすべてのエントリ(ユーザーが含めることを選択した孤立項目)
- Dataverse テーブル —
{ componentType: 1, componentId: MetadataId } - 確認済みのクラウドフロー(Step 5.5 の結果)—
{ componentId: workflowId, componentType: workflowComponentType }(実行時に検出されたタイプを使用) - 確認済みのボットコンポーネント —
{ componentId: botId, componentType: botComponentType }(実行時に検出されたタイプを使用) - 確認済みのクラウドフローで使用される接続参照(Step 5.2 Query G の結果)— 参照ごとに 1 エントリ:
{ componentId: connectionReferenceId, componentType: connectionReferenceComponentType, addRequired: false }。connectionReferences = []の場合はスキップしてください。実行時に解決されたconnectionReferenceComponentTypeを使用してください。ハードコードしないでください(テナントによって10137や10160などの値が確認されています。この値は環境固有です)。これらのエントリがない場合、ソリューションのエクスポートは正常に完了しますが、ターゲットへのインポートはMissingDependencyエラーで失敗します。deploy-pipelinePhase 6.6.1 では、これが「接続参照の不足」検証エラーとして表示されます。 - 採用した孤立 ppc(Step 5.4c の結果)—
{ componentId: ppc.id, componentType: subComponentType, addRequired: false }。Step 5.1 でdiscover-component-types.jsによって解決されたsubComponentTypeの値を使用してください。ハードコードしないでください。Dataverse は Entity 以外のコンポーネントでこのフラグを拒否するため、DoNotIncludeSubcomponents: trueは設定しないでください(HTTP 4000x80040216)。
単一ソリューションモード(MULTI_SOLUTION_MODE = false):配列を一時ファイル(例:C:/Users/{user}/AppData/Local/Temp/components-to-add.json)に書き込み、次を実行します。
node "${PLUGIN_ROOT}/scripts/lib/add-components-to-solution.js" \
--envUrl "{envUrl}" \
--componentsFile "C:/Users/{user}/AppData/Local/Temp/components-to-add.json" \
--solutionUniqueName "{solutionUniqueName}"
複数ソリューションモード(MULTI_SOLUTION_MODE = true):統合されたコンポーネントリストを、各ソリューションのcomponentTypes(Strategy 3ではtableLogicalNamesも)に基づいてPROPOSED_SOLUTIONSの各ソリューションに振り分け、add-components-to-solution.jsをソリューションごとに1回実行します。ソリューションごとのループは順次実行してください(各ヘルパー呼び出しは内部でバッチ処理とトークン更新を行います。ソリューションを並列実行しても、バッチ内のコンポーネントごとのDataverse呼び出しがボトルネックであるため、実時間の短縮にはつながらず、トークン更新の負荷が増えるだけです)。PROPOSED_SOLUTIONSの各エントリについて、(isFutureBuffer: trueのものはスキップして)次の処理を行います。
- 統合コンポーネント配列をフィルタリングし、Dataverseの型名がこのソリューションの
componentTypes配列に対応するコンポーネントだけを残します。数値のcomponentTypeから名前へのマッピングには、discover-component-types.jsとdiscover-site-components.jsが使用するもの(PPC_TYPE_LABELS)と同じものを使います。テーブルは、テーブルの論理名がtableLogicalNamesに含まれるソリューション(Strategy 3)、または'Table'を指定するソリューション(Strategies 1と2)に振り分けます。 - ソリューションごとのサブ配列を一時ファイル(例:
C:/Users/{user}/AppData/Local/Temp/components-{uniqueName}.json)に書き込み、必須の3つのフラグをすべて指定してヘルパーを呼び出します。node "${PLUGIN_ROOT}/scripts/lib/add-components-to-solution.js" \ --envUrl "{envUrl}" \ --componentsFile "C:/Users/{user}/AppData/Local/Temp/components-{uniqueName}.json" \ --solutionUniqueName "{proposedSolutions[i].uniqueName}"uniqueNameをキーとしてJSONサマリーを取得し、一時ファイルを削除します。3つのフラグはすべて必須です。--envUrlまたは--componentsFileを省略し(--solutionUniqueNameのみを指定して)ヘルパーを呼び出すと、--envUrl is required/--componentsFile is requiredでヘルパーが終了コード1で終了します。ループの全反復で--envUrlの値が同じでも、両方のフラグを毎回指定する必要があります。 - コンポーネントの型がどのソリューションの
componentTypesにも一致しない場合は、コンポーネントごとに警告を表示して停止します。振り分けでコンポーネントが失われています。通常、これは分割計画で型が抜け落ちたこと(compute-split-plan.jsのリグレッション)を意味します。コンポーネントがDefaultに黙って漏れないよう、ユーザーは再計画する必要があります。
ヘルパーによる名前での解決が不十分な場合は、Phase 4のSOLUTIONS_BY_NAMEを使用して、各uniqueNameからsolutionIdを解決します。
このスクリプトは20回の呼び出しごとにトークンを更新し、「すでにソリューションに含まれている」場合も成功として扱い、{ total, success, skipped, failed, failures }のJSONサマリーを出力します。完了後、一時ファイルを削除します。
フェーズ 6 — 検証してマニフェストを書き込む
- コンポーネントを検証します:
GET {envUrl}/api/data/v9.2/solutioncomponents?$filter=_solutionid_value eq '{solutionId}'&$select=objectid,componenttype - 種類ごとにコンポーネント数を数え、ウェブサイトレコード(
websiteComponentTypeを使用)が含まれていることを確認します
2b. レンダリングされた ALM プラン用に、セットアップ後の環境変数スナップショットを取得します。 docs/alm/ が存在することを確認してから、検出ヘルパーを実行し、その出力をサイドカーマーカーファイル(docs/alm/last-env-vars.json)に書き込みます。プラン更新ヘルパー(フェーズ 7 の自己更新)はこのサイドカーを planData.envVars に取り込み、レンダリングされたプランの「Env Variables」タブに setup-solution が作成または採用した定義を表示します(これがない場合、フェーズ 5.4/5.4.C/5.4b で定義を作成してもタブは空のままです):
node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"
node "${PLUGIN_ROOT}/scripts/lib/discover-env-var-definitions.js" \
--envUrl "{envUrl}" \
--publisherPrefix "{publisherPrefix}" \
--websiteRecordId "{websiteRecordId}" \
--token "{token}" > docs/alm/last-env-vars.json.tmp \
&& mv docs/alm/last-env-vars.json.tmp docs/alm/last-env-vars.json
一時ファイルに書き込むパターンにより、一時的な検出失敗が発生しても、以前の正常な docs/alm/last-env-vars.json が保持されます(plan-alm フェーズ 1 の docs/alm/alm-size-estimate.json のパターンと同様です)。ヘルパーがゼロ以外の終了コードを返した場合は、標準エラー出力を記録して続行してください。既存のサイドカー(またはサイドカーが存在しない状態)で問題ありません。この実行では、更新時に環境変数が更新されないだけです。
サイドカーの形式は、discover-env-var-definitions.js がすでに返す形式と同じです:{ envVars: [{ schemaName, type, defaultValue, siteSetting }], count }。変換しないでください。レンダラーはこれらのフィールドを直接読み取ります。
-
プロジェクトのルート(
powerpages.config.jsonと同じ場所)に.solution-manifest.jsonを書き込みます:- マニフェストの形式については、
${PLUGIN_ROOT}/references/solution-api-patterns.mdのセクション 7 を参照してください - クラウドフローが確認された場合は、
cloudFlows配列を含めます:[{ "workflowId": "...", "name": "...", "status": "active|inactive" }] - ボットコンポーネントが確認された場合は、
botComponents配列を含めます:[{ "botId": "...", "name": "..." }] - フロー/ボットが検出も確認もされなかった場合は、これらの配列を完全に省略します(不在=追跡対象外、
[]=追跡対象だが選択なし)
MULTI_SOLUTION_MODEでは、solutions[]配列を含むマニフェスト v2 を書き込みます:{ "schemaVersion": 2, "publisher": { "publisherId": "...", "uniqueName": "...", "friendlyName": "...", "customizationPrefix": "..." }, "solutions": [ { "uniqueName": "IdeaSphere_Core", "solutionId": "...", "version": "1.0.0.0", "order": 1, "componentTypes": ["Table", "Site Setting", ...], "components": [ { "componentId": "...", "componentType": 1, "description": "..." } ] }, { "uniqueName": "IdeaSphere_WebAssets", "solutionId": "...", "version": "1.0.0.0", "order": 2, "componentTypes": ["Web File"], "components": [ ... ] } ], "splitStrategy": "strategy-1-layer", "assetAdvisory": [ /* pass-through from plan context */ ] }v1 の単一ソリューションマニフェストは、後方互換性を維持します。 読み取り側(
export-solution、import-solution、setup-pipeline、deploy-pipeline)はschemaVersionを確認します:schemaVersionがない、または1→ 単一ソリューションとして扱います(既存の動作)。schemaVersion: 2→orderに従ってsolutions[]を反復処理します。
- マニフェストの形式については、
-
コミットします:
git add .solution-manifest.json && git commit -m "Add solution manifest for ALM"
フェーズ 7 — 概要の提示
概要テーブルを表示します。
| 項目 | 値 |
|---|---|
| 発行元 | {friendlyName}({uniqueName}、プレフィックス:{prefix}) |
| ソリューション | {friendlyName}({uniqueName}、v{version}) |
| ソリューション ID | {solutionId} |
| 追加されたコンポーネント | N |
| 追加された環境変数の定義 | N(OAuth シークレットが変換された場合) |
| マニフェストの書き込み先 | .solution-manifest.json |
認証設定が環境変数に昇格された場合、各サイト設定が自動的にリンクされたことを確認します。簡潔な確認メッセージを表示します。
Auth settings promoted to environment variables:
✓ Authentication/Registration/LocalLoginEnabled → ids_authentication_registration_localloginenabled
✓ Authentication/Registration/AzureADLoginEnabled → ids_authentication_registration_azureadloginenabled
注:環境ごとの値は、引き続き configure-env-variables または Power Pages 管理 UI から設定する必要があります。
authNoValue 設定が含まれている場合、リマインダーを表示します。
Auth settings included without a dev value (configure in each target env after deploy):
⚠ Authentication/OpenAuth/Facebook/AppId
⚠ Authentication/Registration/LoginButtonAuthenticationType
🚦 ゲート(plan · setup-solution:7.next-step): 後続のデプロイスキルのルーティング先を選択します — PP Pipelines、手動でのエクスポート/インポート、または保留。このスキルの Dataverse への書き込みはすべて完了しています。このゲートで次に実行する処理を選択します。
AskUserQuestion を使って、ユーザーに次に実行する処理を尋ねます。
「このソリューションを他の環境にどのようにデプロイしますか?」
選択肢:
- 「Power Platform Pipelines を使う(推奨)」 — PP Pipelines のホスト環境にパイプラインを設定します。段階的なデプロイ、承認ゲート、各段階での環境変数の上書きに対応しています。
- 「手動でエクスポートしてインポートする」 — ソリューションを zip としてエクスポートし、対象環境に直接インポートします。単発のデプロイに適しています。
- 「後で決める」 — 次のステップの候補を表示して終了します。
ユーザーが選択肢 1 を選んだ場合、すぐに /power-pages:setup-pipeline を実行します。
ユーザーが選択肢 2 を選んだ場合、すぐに /power-pages:export-solution を実行します。
ユーザーが選択肢 3 を選んだ場合、次を表示します。
- 自動化された段階的デプロイを行うには、
/power-pages:setup-pipelineを実行します - 手動インポート用に zip をエクスポートするには、
/power-pages:export-solutionを実行します - 段階ごとに環境固有の値を設定する必要がある場合は、
/power-pages:configure-env-variablesを実行します
ヒント:後からコンポーネントを追加する
稼働中のサイトがこのソリューションに含まれる範囲を超えて拡張された場合 —
add-server-logicによるサーバーロジック、add-cloud-flowによるクラウドフロー、setup-authまたはconfigure-env-variablesによる環境変数、setup-datamodelによる新しいテーブル、または新しい Web ロールなど —/power-pages:setup-solutionを再実行してください。プロジェクトルートに.solution-manifest.jsonが存在する場合、スキルは同期モードを自動検出して検出処理を実行し、稼働中のサイトとソリューションの差分を確認し、バージョンを更新して、不足しているコンポーネントのみを追加します。別途「ソリューションに追加」するワークフローを実行する必要はありません。
スキルの使用状況を記録する
参照先:
${PLUGIN_ROOT}/references/skill-tracking-reference.md
参照先のスキル追跡手順に従って、このスキルの使用状況を記録します。--skillName "SetupSolution" を使用してください。
ALM プランを更新する(存在する場合)
node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
--projectRoot "." \
--phase setup-solution \
--render
このヘルパーは planData.plannedEnvVarCount を 0 にリセットし(計画されていた環境変数は作成済みか、ユーザーの要請によりスキップされています)、docs/alm-plan.html を再レンダリングして、Overview の統計カードと Env Variables タブがセットアップ後の状態を反映するようにします。docs/.alm-plan-data.json が存在しない場合(ALM プランの一部ではなく単独で呼び出された場合)、ヘルパーはソフトなノーオペレーションとして ok:false を返します。そのため、無条件に実行しても安全です。
ユーザーに次のステップを示す(ユーザー主導の順序付け)。 ヘルパーの stdout JSON には nextStep: { name, skill: string | null } | null が含まれます。次に保留中のステップにユーザーが呼び出せるコマンドがない場合(例: 内部的な「Finalize」ステップ)、skill は null になります。そのため、これで分岐してください。
nextStep.skillが null でない → 「プランを更新しました。プランの次のステップ: {nextStep.name} → 準備ができたら{nextStep.skill}を実行してください。」nextStep.skillがnull→ コマンドを付けずにステップ名のみを伝えます: 「プランを更新しました。プランの次のステップ: {nextStep.name}。」run nullと出力してはいけません。
nextStep 自体が null の場合(計画されたすべてのステップが完了)や、ヘルパーが ok:false を返した場合(ディスク上にプランがない)は、次のステップについて何も言及しないでください。次のスキルを自動で呼び出さないでください — plan-alm はプランナーであり、ユーザーが一度に 1 つのスキルずつ実行を主導します。
重要な意思決定ポイント(ユーザーを待つ)
- フェーズ 2: パブリッシャープレフィックスの確認 — 永続的で、変更できません
- フェーズ 3: 再利用か新規作成かの確認 — 書き込みを行う前に
- フェーズ 1、ステップ 5: ALM プランのコンテキスト — plan-alm から事前に読み込まれたサイト設定の分類を使用するか、再検出して再分類するか
- フェーズ 5、ステップ 5.4: 値を持つ認証設定 — 環境変数に昇格させるものとプレーンなサイト設定として保持するものを複数選択。資格情報スタイルの設定については資格情報ごとにプロンプト(Secret 環境変数 / String 環境変数 / スキップ)
- フェーズ 5、ステップ 5.5: マニフェスト全体のレビュー — ユーザーがすべて(ウェブサイト、サイト言語、すべてのコンポーネントカテゴリ、テーブル、環境変数定義、authNoValue 警告)を確認し、コンポーネントが書き込まれる前に承認または調整します
- フェーズ 7: 次のステップ — PP Pipelines(推奨)vs 手動でのエクスポート/インポート vs 後で決定
エラー処理
- パブリッシャーの作成が「duplicate」エラーで失敗した場合: 再クエリして既存のパブリッシャーを使用します
- ソリューションの作成が「duplicate」エラーで失敗した場合: 再クエリして既存のソリューションを使用します
AddSolutionComponentが「already in solution」を返した場合: 成功として扱います(冪等)- 失敗時にロールバックを試みないでください — 何が成功し、何が失敗したかを報告します
進捗トラッキングテーブル
| タスクの件名 | activeForm | 説明 |
|---|---|---|
| 前提条件を確認する | 前提条件を確認中 | PAC CLI の認証を確認し、Azure CLI トークンを取得し、API アクセスを検証し、powerpages.config.json を特定します |
| ソリューション構成を収集する | ソリューション構成を収集中 | ユーザーからパブリッシャー名、プレフィックス、ソリューション名、バージョンを収集します — 取り消せない選択を確認します |
| 既存のパブリッシャーとソリューションを確認する | 既存の状態を確認中 | 重複作成を避けるため、Dataverse に対して既存のパブリッシャーとソリューションをクエリします |
| パブリッシャーとソリューションを作成する | パブリッシャーとソリューションを作成中 | パブリッシャーとソリューションを Dataverse OData API に POST し、ID を取得します |
| サイトコンポーネントをソリューションに追加する | サイトコンポーネントを追加中 | ランタイムフィールドのイントロスペクションを通じてウェブサイト/言語/powerpagecomponents/テーブル/クラウドフロー(タイプ 33)/ボットコンシューマー(タイプ 27)を検出します。サイト設定をカテゴリ別に分割します。CLOUD FLOWS セクションと BOT CONSUMERS セクションを含む完全なマニフェストを、アクティブ/非アクティブのステータス付きで提示します。ユーザーの確認を取得します。ウェブサイト、サイト言語、確認済みのすべてのコンポーネント、テーブル(ComponentType=1)、確認済みのクラウドフロー、確認済みのボットコンポーネントに対して add-components-to-solution.js を呼び出します |
| 検証してマニフェストを書き込む | ソリューションを検証してマニフェストを書き込み中 | ソリューション内のコンポーネントを確認し、.solution-manifest.json を書き込み、コミットします |
| サマリーを提示する | サマリーを提示中 | ソリューションの詳細、コンポーネント数、次のステップを表示します |