mcpField
A
add-pdf-report

add-pdf-report

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

add-pdf-report について

Power Apps モバイルプロジェクトで、ローカル PDF レポート用ラッパーを追加・確認する内部スキルです。アプリ側の HTML を `expo-print` で PDF 化し、`expo-sharing` が導入済みの場合のみ共有に対応します。

主な機能

  1. 01ローカル PDF ファイルの生成
  2. 02決定的なレポート HTML の構築
  3. 03expo-sharing による共有(任意)
  4. 04Dataverse File 列へのアップロード対応
  5. 05互換性と型エラーの確認

活用シーン

  1. 01アプリのレコードからレポートを作成
  2. 02領収書や証明書を生成
  3. 03生成 PDF のプレビューやアップロード
  4. 04expo-sharing 導入済み環境で共有を追加

インストール

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

$npx skills add https://github.com/microsoft/power-platform-skills --skill add-pdf-report

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

詳細

共通の指示:shared-instructions.md - 最初にお読みください。

作業ディレクトリ: プロジェクトの読み取りやコマンドの実行に先立ち、 native-artifact-compatibility.md Step 0を実行してください。 /add-nativeで解決された絶対パスのworking_dirを引き継ぎ、このヘルパーが別のディレクトリから開始された場合も含め、すべてのシェル呼び出しとファイルツールをそこに紐付けてください。

PDF レポートの追加

内部ヘルパー。 ユーザーは/add-native pdf-report、/add-native generate-pdf、または/add-native pdf-exportを呼び出してください。/add-nativeは機能を解決した後、ここにルーティングします。

レコード、証拠、証明書、領収書、または概要から作成されたアプリ所有のPDFについて、ローカルPDFレポートラッパーを生成または検証します。このヘルパーはexpo-printを使用して、ローカルPDFファイルURIを作成します。expo-sharingは、すでに存在する場合に限り使用できます。パッケージのインストールやネイティブPDFビューアーの直接インポートは行いません。

機能の範囲

ユーザーのニーズ適切な方法
アプリのデータからレポートを生成/エクスポート/印刷するこのヘルパー:expo-print -> ローカルPDF URI
生成したローカルPDFをデバイスから共有するexpo-sharingがすでにpackage.jsonにある場合に限り、共有メソッドを追加
生成したPDFをDataverseに保持するまず親行を作成/更新し、その後、生成サービスを使用してDataverseのFile列にアップロード
Power Appsのネイティブビューアーで既存のHTTPSまたはローカルファイルのPDFを開く/add-native pdf-viewer。ただし、@microsoft/power-apps-native-pdf-viewer 0.2.9+がすでに存在する場合に限る
ユーザーが選択したPDFを選択/インポート/アップロードするDataverseのFile列では/add-native document-pickerまたはホスト側の<FilePicker>

ローカルで生成されたPDFは通常file:// URIであり、@microsoft/power-apps-native-pdf-viewer 0.2.9+を使ってopenHttpsPdf(...)に渡すことができます。

手順

1. アプリを確認する
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
test -f app.config.js && test -f power.config.json && test -f package.json && test -d src || { echo "BLOCKED: working_dir is not an initialized app" >&2; exit 1; }

これが失敗した場合は、まず/create-mobile-appを実行するようユーザーに伝え、そこで停止してください。

2. パッケージがすでに存在することを確認する

expo-printは必須です。共有機能が必要な計画でない限り、expo-sharingは任意です。

cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node -e "const p=require('./package.json'); const deps={...p.dependencies,...p.devDependencies}; const required='expo-print'; if (!deps[required]) { console.error('MISSING: expo-print is not in package.json. The template/app must already ship it for /add-native pdf-report. This skill will not install it or edit native config. Capability not added.'); process.exit(1); } console.log('OK: expo-print package present'); console.log(deps['expo-sharing'] ? 'OK: expo-sharing package present' : 'OPTIONAL_MISSING: expo-sharing is not in package.json; generated PDFs can be created/viewed/uploaded, but sharing helpers must not be generated.');"

expo-printがない場合は、停止してください。npm install、npx expo install、pod installを実行したり、app.config.jsを編集したりしないでください。このアプリの計画や生成されるラッパーにpdf-reportを追加しないでください。

expo-sharingがない場合は:

  • 生成のみ、ネイティブビューアーでのプレビュー、またはDataverseへのアップロードのフローは続行します。
  • expo-sharingをインポートしないでください。
  • sharePdfReport(...)を生成しないでください。
  • ユーザーの要件に共有が明示的に含まれている場合は、停止して、このテンプレートでは共有がサポートされていないことを伝えてください。
2a. 要求された成果物を調整する

書き込みや再利用の前に、PDFレポート行についてnative-artifact-compatibility.mdの 手順1~3を読み、実行してください。 /add-nativeから現在の承認/モードを引き継ぎ、有効なスコープ付き承認を繰り返し求めないでください。 src/native/pdfReport.tsを確認し、要求されたAPI全体を検証してください:createPdfReport、 wrapPdfDocument、escapePdfHtml、保持用に要求されたincludeBase64の出力、 および共有が承認されている場合のsharePdfReport/PdfShareResult。生成のみの互換性では、共有/アップロードの要求を満たしません。ローカルファイルと生成されたFileサービスのペイロード要件を検証してください。必須のストレージ要件を満たさない場合、または承認の範囲外に互換性のない成果物がある場合は、NEEDS_CONTEXTをオーナーに返すか、単独での実行時にはユーザーに確認してください。

3. src/native/pdfReport.ts の作成または確認

src/native/pdfReport.ts について、Step 2a の判断を適用します。要求されていて存在しない場合は作成し、互換性がある場合のみ変更せず再利用するか、承認された範囲の更新のみを行います。独自の HTML ロジックと既存のエクスポートを維持してください。この例は上書き用テンプレートではありません。

ラッパーは次の要件を満たす必要があります。

  • Step 2 で存在が確認された後にのみ expo-print をインポートします。
  • Step 2 で存在が確認された場合にのみ expo-sharing をインポートします。
  • 判別可能なユニオン型を返し、例外をスローしません。
  • 生成されたローカル PDF は、表示/共有/アップロードのフローでローカルファイルとして扱います。
  • このラッパーから @microsoft/power-apps-native-pdf-viewer を直接インポートしません。
  • HTML 生成は決定的でアプリが管理するものとし、ラッパー内でリモート HTML を取得しません。

共有が要求され、expo-sharing が存在する場合の基本ラッパー:

// src/native/pdfReport.ts
import * as Print from 'expo-print';
import * as Sharing from 'expo-sharing';

export type PdfReportResult =
  | { ok: true; uri: string; numberOfPages?: number; base64?: string }
  | { ok: false; reason: 'EMPTY_HTML' | 'PRINT_FAILED'; message?: string };

export type PdfShareResult =
  | { ok: true }
  | { ok: false; reason: 'INVALID_URI' | 'SHARING_UNAVAILABLE' | 'SHARE_FAILED'; message?: string };

export function escapePdfHtml(value: string): string {
  return value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

export function wrapPdfDocument(input: { title: string; bodyHtml: string; styles?: string }): string {
  const title = escapePdfHtml(input.title.trim() || 'Report');
  return `<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>${title}</title>
    <style>
      body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 32px; color: #111827; }
      h1, h2, h3 { margin: 0 0 12px; }
      table { width: 100%; border-collapse: collapse; }
      th, td { border-bottom: 1px solid #e5e7eb; padding: 8px; text-align: left; }
      ${input.styles ?? ''}
    </style>
  </head>
  <body>${input.bodyHtml}</body>
</html>`;
}

export async function createPdfReport(
  html: string,
  options?: { includeBase64?: boolean },
): Promise<PdfReportResult> {
  if (!html.trim()) {
    return { ok: false, reason: 'EMPTY_HTML', message: 'PDF report HTML is empty.' };
  }

  try {
    const result = await Print.printToFileAsync({
      html,
      base64: options?.includeBase64 ?? false,
    });

    return {
      ok: true,
      uri: result.uri,
      numberOfPages: result.numberOfPages,
      base64: result.base64,
    };
  } catch (error: any) {
    return { ok: false, reason: 'PRINT_FAILED', message: error?.message ?? String(error) };
  }
}

export async function sharePdfReport(uri: string, options?: { dialogTitle?: string }): Promise<PdfShareResult> {
  if (!uri || !uri.startsWith('file://')) {
    return { ok: false, reason: 'INVALID_URI', message: 'Generated PDF reports must be shared from a local file URI.' };
  }

  try {
    const available = await Sharing.isAvailableAsync();
    if (!available) {
      return { ok: false, reason: 'SHARING_UNAVAILABLE', message: 'Sharing is not available on this platform.' };
    }

    await Sharing.shareAsync(uri, {
      mimeType: 'application/pdf',
      UTI: 'com.adobe.pdf',
      dialogTitle: options?.dialogTitle ?? 'Share PDF report',
    });

    return { ok: true };
  } catch (error: any) {
    return { ok: false, reason: 'SHARE_FAILED', message: error?.message ?? String(error) };
  }
}

新しい生成のみのラッパーを作成する場合(または expo-sharing が存在しない場合)は、expo-sharing のインポート、PdfShareResult、および sharePdfReport(...) を省略します。createPdfReport(...)、wrapPdfDocument(...)、escapePdfHtml(...) は維持してください。今回共有が要求されていないという理由だけで、互換性のある既存の共有エクスポートを削除しないでください。既存の互換性のないインポートについては、Step 2a の範囲限定更新の条件に従います。

4. ラッパーの使用

画面をここで編集するための指示ではなく、担当者向けの統合ガイダンスです。画面からは Expo モジュールを直接ではなく、ラッパーをインポートします。この例では、承認され、利用可能な場合にのみ共有を含めます。

import { createPdfReport, escapePdfHtml, sharePdfReport, wrapPdfDocument } from '@/native/pdfReport';

const html = wrapPdfDocument({
  title: 'Inspection report',
  bodyHtml: `<h1>Inspection report</h1><p>${escapePdfHtml(summary)}</p>`,
});

const report = await createPdfReport(html);
if (!report.ok) {
  showError(report.message ?? 'Report PDF was not generated.');
  return;
}

const share = await sharePdfReport(report.uri, { dialogTitle: 'Share inspection report' });
if (!share.ok) {
  showError(share.message ?? 'Report PDF was generated but could not be shared.');
}

expo-sharing が存在しない場合でも、画面から createPdfReport(...) を呼び出し、返された file:// URI をネイティブ PDF ビューアー 0.2.9 以降でプレビューしたり、生成されたサービス経由で Dataverse の File 列にアップロードしたりできます。Share ボタンを表示してはいけません。

5. Dataverse への任意のアップロード

任意とは、要求されていないという意味であり、保持が承認されている場合に省略できるという意味ではありません。Step 2a でラッパーのペイロード対応と生成されたシグネチャを確認します。担当者は、以下のアプリ固有のアダプター/画面統合を実装します。ヘルパーが担当すべきサポートが不足している場合や、必要なストレージが不明な場合は、キャプチャのみ成功したものとして扱わず、NEEDS_CONTEXT を返します。

保持する PDF には Dataverse の File 列を使用します。まず親行を保存または更新し、success を確認してから、生成されたサービスの upload(id, columnName, file, fileDisplayName?) シグネチャと互換性のあるペイロードをアップロードします。File 列のバイトデータを create/update の JSON に含めてはいけません。

const save = await Cr123_inspectionService.update(inspectionId, {
  cr123_reportgeneratedat: new Date().toISOString(),
});

if (!save.success) {
  showError(save.error?.message ?? 'Inspection was not saved.');
  return;
}

const report = await createPdfReport(html, { includeBase64: true });
if (!report.ok) {
  showError(report.message ?? 'Report PDF was not generated.');
  return;
}

if (!report.base64) {
  showError('Report PDF was generated but could not be prepared for upload.');
  return;
}

// Convert report.base64 into the File/blob/picked-file shape expected by the generated service.
// Do not pass report.uri unless the generated service explicitly documents URI support.
const reportFile = createUploadFileFromBase64(report.base64, 'inspection-report.pdf', 'application/pdf');

const upload = await Cr123_inspectionService.upload(
  inspectionId,
  'cr123_reportfile',
  reportFile,
  'inspection-report.pdf',
);
if (!upload.success) {
  showError(upload.error?.message ?? 'Report PDF was not uploaded.');
}

生成されたアップロードヘルパーがブラウザーの File、ホストの PickedFileInfo、バイトデータ、またはサービス固有の別のペイロード形式を想定する場合があるため、createUploadFileFromBase64(...) は意図的にアプリ固有の実装とします。そのテーブル用に生成されたモデル/サービスの型を使用し、生成済みのサービスは編集しないでください。

6. 型チェック
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
npx --no-install tsc --noEmit

対象範囲内のヘルパーのエラーのみを修正し、共有互換性契約の Step 4 を実行します。空の HTML、共有機能が利用できない場合、印刷/共有の失敗、必須の base64 がない場合も含め、要求された生成、共有、保持のペイロードを再確認します。型チェックの成功だけでは不十分です。画面/生成済みファイルは修正しないでください。

7. ネイティブ再ビルドに関する注意

このスキルではネイティブコードをインストールしません。このスキルの範囲外で expo-print または expo-sharing を追加したばかりの場合、アプリにはこのワークフローの範囲外でネイティブ再ビルドが必要です。パッケージがすでにビルドに含まれている場合は、ラッパーの編集に Metro のホットリロードだけで十分です。

8. 概要

この概要の前に、共有互換性の結果と、実際に作成/更新/再利用したパスを返します。担当者側で行うアップロード/統合は別途報告します。成功した場合にのみ memory-bank を更新し、オーケストレーション時の最終更新は担当者に委ねます。

ユーザーに次のように伝えてください。

PDF report helper added
Required package : expo-print
Optional share   : expo-sharing <present | absent>
Wrapper          : src/native/pdfReport.ts
Output           : local PDF file URI
Native viewer    : optional; 0.2.9+ can open the generated file:// URI
Type-check       : PASS
Native rebuild   : not performed by this skill

Controls の下にある memory-bank.md を更新してください。

- PDF report helper added - expo-print local generation, expo-sharing <present|absent> (<ISO date>)

コメント