/** * resolve-project.js * ------------------------------------------------------------ * 各スクリプトを ClaudePleasanter直下から直接実行する運用に合わせ、 * 「どのプロジェクト(サイト)フォルダに対して実行するか」「どのサーバー環境 * (本番/テスト)に対して実行するか」を解決する共通ヘルパー。 * * 使い方(各スクリプトの実行時): * node .claude/js/get-site-config.js --project="IC(東京)【引継依頼】" --env=production * node .claude/js/get-site-config.js -p IC東京 --env=development * (フォルダ名の完全一致が無い場合、部分一致(大文字小文字無視)で1件に絞れれば採用する) * * --env は production(本番: nextoffice.next-hd.co.jp) / development(テスト: neo999.next-hd.net) / * staging(検証: nextoffice2.next-hd.net、2026-08-29追加)のいずれかを必須指定する * (誤送信事故防止のためデフォルト値は設けない)。 * 接続先情報(BaseUrl/ApiKey/ApiVersion)はリポジトリルート直下の * config_production.json / config_development.json / config_staging.json に集約されており、 * loadServerConfig(env) で読み込む。各プロジェクトのSiteIdはプロジェクト直下の * siteid.json({"SiteId": "..."}) に置き、loadSiteId(baseDir) で読み込む * (旧config.jsonからの移行。config.jsonにはBaseUrl/ApiKeyも同居していたため、 * 本番/テストのconfigsデータが同一フォルダに混在する事故要因になっていた)。 * * 複数コマンドを同じプロジェクト向けに連続実行する場合は、--project の代わりに * 環境変数 PLEASANTER_PROJECT_ROOT にプロジェクトフォルダの絶対パスを設定してもよい * (--project指定があればそちらを優先する)。--env も同様に環境変数 * PLEASANTER_ENV で指定可能(--env指定があればそちらを優先する)。 * ------------------------------------------------------------ */ const fs = require("fs"); const path = require("path"); const REPO_ROOT = path.join(__dirname, "..", ".."); const VALID_ENVS = ["production", "development", "staging"]; function listProjectCandidates() { return fs.readdirSync(REPO_ROOT).filter((name) => { if (name.startsWith(".")) return false; const full = path.join(REPO_ROOT, name); return fs.statSync(full).isDirectory(); }); } // argvから --project=X / --project X / -p X を取り除き、{ value, argv(残り) } を返す function extractProjectFlag(argv) { const rest = []; let value = null; for (let i = 0; i < argv.length; i++) { const a = argv[i]; if (a.startsWith("--project=")) { value = a.slice("--project=".length); } else if (a === "--project" || a === "-p") { value = argv[++i]; } else { rest.push(a); } } return { value, argv: rest }; } // argvから --env=production|development を取り除き、{ value, argv(残り) } を返す function extractEnvFlag(argv) { const rest = []; let value = null; for (let i = 0; i < argv.length; i++) { const a = argv[i]; if (a.startsWith("--env=")) { value = a.slice("--env=".length); } else if (a === "--env") { value = argv[++i]; } else { rest.push(a); } } return { value, argv: rest }; } function printCandidates() { console.error("利用可能なプロジェクト:"); for (const c of listProjectCandidates()) console.error(` - ${c}`); } // argv(既定: process.argv.slice(2))からサーバー環境(production/development)を解決する。 // 未指定・不正値は誤送信事故防止のためエラー終了する(デフォルト値は設けない)。 // 戻り値: { env: "production"|"development", argv: --env指定を取り除いた残りの引数配列 } function resolveEnv(argv) { const { value, argv: rest } = extractEnvFlag(argv); const env = value || process.env.PLEASANTER_ENV; if (!env) { console.error("[エラー] サーバー環境が指定されていません。"); console.error('使い方: --env=production (本番) / --env=development (テスト) / --env=staging (検証) のいずれかを指定するか、環境変数 PLEASANTER_ENV を設定してください。'); process.exit(1); } if (!VALID_ENVS.includes(env)) { console.error(`[エラー] 不正な --env 値です: ${env}`); console.error(`指定可能な値: ${VALID_ENVS.join(" / ")}`); process.exit(1); } return { env, argv: rest }; } // argv(既定: process.argv.slice(2))からプロジェクトフォルダとサーバー環境を解決する。 // 戻り値: { baseDir: 絶対パス, env: "production"|"development", argv: 残りの引数配列 } function resolveProjectRoot(argv = process.argv.slice(2)) { const { value, argv: afterProject } = extractProjectFlag(argv); const { env, argv: rest } = resolveEnv(afterProject); const name = value || process.env.PLEASANTER_PROJECT_ROOT; if (!name) { console.error("[エラー] 対象プロジェクトが指定されていません。"); console.error('使い方: --project="<フォルダ名>" を指定するか、環境変数 PLEASANTER_PROJECT_ROOT にプロジェクトフォルダの絶対パスを設定してください。'); printCandidates(); process.exit(1); } if (path.isAbsolute(name) && fs.existsSync(name)) return { baseDir: name, env, argv: rest }; const direct = path.join(REPO_ROOT, name); if (fs.existsSync(direct) && fs.statSync(direct).isDirectory()) return { baseDir: direct, env, argv: rest }; const candidates = listProjectCandidates().filter((c) => c.toLowerCase().includes(name.toLowerCase())); if (candidates.length === 1) return { baseDir: path.join(REPO_ROOT, candidates[0]), env, argv: rest }; if (candidates.length > 1) { console.error(`[エラー] "${name}" に一致するプロジェクトが複数見つかりました: ${candidates.join(", ")}`); console.error("フォルダ名をフルで指定してください。"); process.exit(1); } console.error(`[エラー] プロジェクトフォルダが見つかりません: ${name}`); printCandidates(); process.exit(1); } // リポジトリルート直下の config_{env}.json(BaseUrl/ApiKey/ApiVersion。 // LoginId/Password等はget-page-html.js用の任意項目としてそのまま透過)を読み込む。 function loadServerConfig(env) { if (!VALID_ENVS.includes(env)) { console.error(`[エラー] 不正な env 値です: ${env}`); process.exit(1); } const configPath = path.join(REPO_ROOT, `config_${env}.json`); if (!fs.existsSync(configPath)) { console.error(`[エラー] サーバー接続設定ファイルが見つかりません: ${configPath}`); process.exit(1); } const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); if (!config.BaseUrl || !config.ApiKey) { console.error(`[エラー] ${configPath} に BaseUrl / ApiKey を設定してください。`); process.exit(1); } return config; } // プロジェクト直下の siteid.json({"SiteId": "..."}) からSiteIdを読み込む。 // 旧config.jsonが残っている場合は移行を促すエラーにする。 function loadSiteId(baseDir) { const siteIdPath = path.join(baseDir, "siteid.json"); if (!fs.existsSync(siteIdPath)) { const legacyConfigPath = path.join(baseDir, "config.json"); if (fs.existsSync(legacyConfigPath)) { console.error(`[エラー] ${legacyConfigPath} は廃止されました。siteid.json({"SiteId": "..."}) に移行してください。`); } else { console.error(`[エラー] siteid.json が見つかりません: ${siteIdPath}`); } process.exit(1); } const siteid = JSON.parse(fs.readFileSync(siteIdPath, "utf-8")); if (!siteid.SiteId) { console.error(`[エラー] ${siteIdPath} に SiteId を設定してください。`); process.exit(1); } return siteid.SiteId; } module.exports = { resolveProjectRoot, resolveEnv, loadServerConfig, loadSiteId, REPO_ROOT, VALID_ENVS, listProjectCandidates, };