/** * WPML compatibility functions * * @global array $duplicated_posts Array to store the posts being duplicated. * * @package Yoast\WP\Duplicate_Post * @since 3.2 */ add_action( 'admin_init', 'duplicate_post_wpml_init' ); /** * Add handlers for WPML compatibility. */ function duplicate_post_wpml_init() { if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 ); } } global $duplicated_posts; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: Renaming a global variable is a BC break. $duplicated_posts = []; /** * Copy post translations. * * @global SitePress $sitepress Instance of the Main WPML class. * @global array $duplicated_posts Array of duplicated posts. * * @param int $post_id ID of the copy. * @param WP_Post $post Original post object. * @param string $status Status of the new post. */ function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) { global $sitepress; global $duplicated_posts; remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 ); remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 ); $current_language = $sitepress->get_current_language(); $trid = $sitepress->get_element_trid( $post->ID ); if ( ! empty( $trid ) ) { $translations = $sitepress->get_element_translations( $trid ); $new_trid = $sitepress->get_element_trid( $post_id ); foreach ( $translations as $code => $details ) { if ( $code !== $current_language ) { if ( $details->element_id ) { $translation = get_post( $details->element_id ); if ( ! $translation ) { continue; } $new_post_id = duplicate_post_create_duplicate( $translation, $status ); if ( ! is_wp_error( $new_post_id ) ) { $sitepress->set_element_language_details( $new_post_id, 'post_' . $translation->post_type, $new_trid, $code, $current_language ); } } } } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: see above. $duplicated_posts[ $post->ID ] = $post_id; } } /** * Duplicate string packages. * * @global array() $duplicated_posts Array of duplicated posts. */ function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: renaming the function would be a BC-break. global $duplicated_posts; foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id ); if ( is_array( $original_string_packages ) ) { foreach ( $original_string_packages as $original_string_package ) { $translated_original_strings = $original_string_package->get_translated_strings( [] ); foreach ( $new_string_packages as $new_string_package ) { $cache = new WPML_WP_Cache( 'WPML_Package' ); $cache->flush_group_cache(); $new_strings = $new_string_package->get_package_strings(); foreach ( $new_strings as $new_string ) { if ( isset( $translated_original_strings[ $new_string->name ] ) ) { foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) { do_action( // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. 'wpml_add_string_translation', $new_string->id, $language, $translated_string['value'], $translated_string['status'] ); } } } } } } } } フェーズA/B – Raqqa

フェーズA/B

以下の手順で、一切既存ファイルを編集せずにDocker環境上でnpm run devが動作し、Make出力のUIをブラウザで確認できます。


🧱 1. ルート構成(あなたが作る最小追加ファイル)

frontend/
├── apps/
│   └── top/           ← Figma Make の出力をこの中に丸ごと移動
├── shared/
│   └── api/
│        └── client.ts
├── docker-compose.yml
├── Dockerfile
└── .env

🐋 Dockerfile

# frontend/Dockerfile
FROM node:20-alpine

# 共通作業ディレクトリ
WORKDIR /app

# 最初に依存を解決(apps/top を基準に)
COPY apps/top/package*.json ./
RUN npm install

# Vite 開発ポート
EXPOSE 3000

CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

🧩 docker-compose.yml

version: "3.9"

services:
  top:
    build: .
    working_dir: /app/apps/top
    volumes:
      - ./apps/top:/app/apps/top
      - ./shared:/app/shared
      - /app/node_modules
    ports:
      - "3001:3000"
    environment:
      - VITE_API_BASE_URL=http://api.local:8080

🔹 これで docker compose up tophttp://localhost:3001 へアクセスできます。
🔹 node_modules はコンテナ側にのみ作られるためホストは汚れません。


🌐 .env(共通設定)

VITE_API_BASE_URL=http://api.local:8080

まだAPIは不要でもOK。後でFastAPIやOdooとつなげるときに自動参照されます。


📦 shared/api/client.ts

// frontend/shared/api/client.ts
export async function apiGet(path: string) {
  const base = import.meta.env.VITE_API_BASE_URL || "http://api.local:8080"
  const res = await fetch(`${base}${path}`)
  if (!res.ok) throw new Error(`API error: ${res.status}`)
  return res.json()
}

Make側のコードには何も挿入せず、
今後 import { apiGet } from "@/shared/api/client" で共通利用できます。


🚀 2. Make出力を配置するだけ

あなたのFigma Makeフォルダをそのまま frontend/apps/top に移動:

mv <Figma_Make_export> frontend/apps/top

構成イメージ:

frontend/
├── apps/
│   └── top/
│       ├── package.json
│       ├── vite.config.ts
│       ├── index.html
│       └── src/...
├── shared/api/client.ts
├── docker-compose.yml
├── Dockerfile
└── .env

Zone.Identifier 付きファイルはWindowsのセキュリティ情報なので無視してOKです(削除推奨)。


✅ 3. 起動確認

cd frontend
docker compose up top

アクセス:
👉 http://localhost:3001

Figma Make 由来のUIがそのまま表示されれば成功です 🎉


🧠 4. 「どこまで共通」「どこから個別」にすべきか

管理単位説明
Dockerfile / docker-compose / .env共通すべての画面で同じNode環境を使用
shared/api/共通API呼び出し・認証・設定など再利用
apps/個別(画面ごと)各画面(top, fields, views…)は独立
package.json / vite.config.ts各アプリで独立ポート番号だけ変える(例:3002, 3003 …)
Tailwind / shadcn最初は個別、後に共通化可Makeが既に持つ設定をそのまま使う

つまり:

  • 共通で1つ作っておけば 以降の画面は apps/portal-fields を作り、Make出力をそのままコピーするだけ。
  • 変更は docker-compose.yml に1サービス追加するだけでOK。

💡 例:次の画面(portal-fields)を追加する場合

services:
  top:
    ...
  portal-fields:
    build: .
    working_dir: /app/apps/portal-fields
    volumes:
      - ./apps/portal-fields:/app/apps/portal-fields
      - ./shared:/app/shared
      - /app/node_modules
    ports:
      - "3002:3000"
    environment:
      - VITE_API_BASE_URL=http://api.local:8080

docker compose up portal-fields で2画面目も独立起動。


✅ まとめ

ステップ内容編集
1Dockerfile / compose / shared/api を追加新規
2Figma Makeフォルダを apps/top に移動既存ファイル変更なし
3docker compose up top実行のみ

フェーズB

フェーズA終了後に行うため、ペンディング、2)までは終わっている

1) カレントディレクトリ:リポジトリのルート

# 例: ここがリポジトリのトップ
pwd   # => ~/dev-portal-frontend

2) model_detail 用の openapi フォルダ作成&コピー

# ルートで実行
mkdir -p frontend/apps/model_detail/openapi

# topで保存済みのOpenAPIを使い回す(ファイル名はあなたが保存したものに合わせる)
cp frontend/apps/top/openapi/v1.0.0.json \
   frontend/apps/model_detail/openapi/v1.0.0.json

3) 型生成(typegen)

# カレントディレクトリを frontend に移動
cd frontend

# 画面のdevサーバは起動したままでOK。別ターミナルから実行推奨
docker compose run --rm model_detail \
  npx -y openapi-typescript "openapi/v1.0.0.json" \
  -o src/types/api.d.ts
  • この docker compose run model_detailコンテナ内CWD/app/apps/model_detail です。
    そこから見た openapi/v1.0.0.json を読むので 相対パスのままでOK

4) 成功確認

# ホスト側(あなたの端末)で確認
ls -l apps/model_detail/src/types/api.d.ts
# => ファイルができていればOK(Viteが自動で拾います)

Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です