/** * apply-to-site-496626.js * ------------------------------------------------------------ * configs/site-480111_updated.json のSiteSettingsを、SiteId 496626へ * まるごと上書き適用する(updatesite / Mode:"full")。 * * 通常このプロジェクトの apply-site-config.js はドライラン専用(実送信しない)だが、 * 本スクリプトは今回のタスクに限りユーザーが明示的に許可した「専用の直接実行スクリプト」 * であり、--execute 指定時のみ実際にAPIへPOSTする(curlは使わない)。 * * Permissionsは送信ボディに含めない。496626は権限をInheritPermissionで * 継承しているため、直接の書き込み対象ではない(権限変更は apply-permission-grant.js * を参照)。Title/ReferenceType/ParentId/InheritPermissionは496626自身の * 現状値(configs/site-496626_latest.json)から取得し、SiteSettingsのみ * 480111由来の変更後内容で置き換える。 * * 差分プレビュー・送信結果は、SiteId 496626 の modify/{リクエストラベル}/ フォルダへ保存する * (restructure-site-480111.js が作成したフォルダを --request= で指定するか、省略時は * 最新のmodifyフォルダを自動的に使う)。 * * 使い方: * node apply-to-site-496626.js … 差分表示・アーティファクト生成のみ(送信なし) * node apply-to-site-496626.js --execute … 上記に加えて実際に送信する * node apply-to-site-496626.js --request=ラベル … 対象のmodifyフォルダを明示指定 * ------------------------------------------------------------ */ const fs = require("fs"); const path = require("path"); const { findSiteDir, latestJsonPath, newModifyDir } = require("../../site-paths"); const { REPO_ROOT, loadServerConfig } = require("../../resolve-project"); const PROJECT_NAME = "新・着工要因システム"; const ENV = "production"; const TARGET_SITE_ID = 496626; const baseDir = path.join(REPO_ROOT, PROJECT_NAME); const configsDir = path.join(baseDir, "configs", ENV); const execute = process.argv.includes("--execute"); const requestArg = process.argv.find((a) => a.startsWith("--request=")); const requestLabel = requestArg ? requestArg.split("=")[1] : null; function loadJson(p, required = true) { if (!fs.existsSync(p)) { if (required) { console.error(`[エラー] ファイルが見つかりません: ${p}`); process.exit(1); } return null; } return JSON.parse(fs.readFileSync(p, "utf-8")); } function maskApiKey(key) { if (!key) return key; return key.length > 8 ? key.slice(0, 4) + "****" + key.slice(-4) : "****"; } function timestamp() { return new Date().toISOString().replace(/[:.]/g, "-"); } function diffTopLevel(before, after) { const lines = []; const afterKeys = Object.keys(after || {}); let changed = false; for (const key of afterKeys) { const b = JSON.stringify(before?.[key]); const a = JSON.stringify(after?.[key]); if (b !== a) { changed = true; const bLen = Array.isArray(before?.[key]) ? `(${before[key].length}件)` : ""; const aLen = Array.isArray(after?.[key]) ? `(${after[key].length}件)` : ""; lines.push(` [変更] SiteSettings.${key} ${bLen} → ${aLen}`); } } if (!changed) lines.push(" 差分なし"); return lines; } // 対象のmodifyフォルダを解決する(--request指定があればそれ、無ければ最新のものを使う) const targetSiteDir = findSiteDir(configsDir, TARGET_SITE_ID); if (!targetSiteDir) { console.error(`[エラー] SiteId ${TARGET_SITE_ID} のフォルダが見つかりません。先に node get-site-config.js を実行してください。`); process.exit(1); } function resolveModifyDir() { if (requestLabel) return newModifyDir(targetSiteDir, requestLabel); const modifyRoot = path.join(targetSiteDir, "modify"); if (!fs.existsSync(modifyRoot)) { console.error(`[エラー] modifyフォルダがまだありません。先に node restructure-site-480111.js を実行してください。`); process.exit(1); } const dirs = fs.readdirSync(modifyRoot).filter((n) => fs.statSync(path.join(modifyRoot, n)).isDirectory()); if (dirs.length === 0) { console.error(`[エラー] modifyフォルダの中身が空です: ${modifyRoot}`); process.exit(1); } dirs.sort(); return path.join(modifyRoot, dirs[dirs.length - 1]); } const modifyDir = resolveModifyDir(); console.log(`[INFO] modifyフォルダ: ${modifyDir}`); const config = loadServerConfig(ENV); const updatedPath = path.join(modifyDir, "site-480111_updated.json"); const updatedJson = loadJson(updatedPath); const modifiedSiteSettings = updatedJson?.Response?.Data?.SiteSettings; if (!modifiedSiteSettings) { console.error(`[エラー] ${updatedPath} からSiteSettingsを取得できませんでした。先に node restructure-site-480111.js を実行してください。`); process.exit(1); } const currentPath = latestJsonPath(configsDir, TARGET_SITE_ID); const currentJson = loadJson(currentPath, false); const currentData = currentJson?.Response?.Data; if (!currentData) { console.error(`[エラー] ${currentPath} が見つかりません。先に node get-site-config.js(SiteIdに${TARGET_SITE_ID}を含めて)を実行してください。`); process.exit(1); } const url = `${config.BaseUrl.replace(/\/+$/, "")}/api/items/${TARGET_SITE_ID}/updatesite`; const body = { ApiVersion: config.ApiVersion || "1.1", ApiKey: config.ApiKey, SiteId: TARGET_SITE_ID, Title: currentData.Title, ReferenceType: currentData.ReferenceType, ParentId: currentData.ParentId, InheritPermission: currentData.InheritPermission, SiteSettings: modifiedSiteSettings, }; console.log("========================================"); console.log(` SiteId ${TARGET_SITE_ID} への適用${execute ? "(実行)" : "(プレビューのみ)"}`); console.log("========================================"); console.log(`URL : ${url}`); console.log(`Title : ${currentData.Title}`); console.log(`InheritPermission: ${currentData.InheritPermission}(権限は今回変更しません)`); console.log(""); console.log("--- SiteSettings 差分(496626現状 → 480111由来の変更後) ---"); const diffLines = diffTopLevel(currentData.SiteSettings, modifiedSiteSettings); diffLines.forEach((l) => console.log(l)); console.log(""); const newItem = (modifiedSiteSettings.Columns || []).find( (c) => c.ColumnName === "Date031" && c.GridLabelText === "解体紹介料申請" ); console.log(`新規追加項目: ${newItem ? "解体紹介料申請 (Date031, 担当:" + newItem.Description + ")" : "(見つかりません)"}`); console.log(`Columns件数 : ${(currentData.SiteSettings?.Columns || []).length} → ${modifiedSiteSettings.Columns.length}`); console.log(`Sections件数: ${(currentData.SiteSettings?.Sections || []).length} → ${modifiedSiteSettings.Sections.length}`); console.log(""); const maskedBody = { ...body, ApiKey: maskApiKey(body.ApiKey) }; console.log("--- 送信されるBody(APIキーはマスク表示、SiteSettingsは件数のみ表示) ---"); console.log( JSON.stringify( { ...maskedBody, SiteSettings: `(省略: Columns ${modifiedSiteSettings.Columns.length}件 等)` }, null, 2 ) ); const ts = timestamp(); const diffOutPath = path.join(modifyDir, `site-${TARGET_SITE_ID}_apply_diff_${ts}.txt`); const diffText = [ `SiteId ${TARGET_SITE_ID} 適用差分プレビュー (${ts})`, `URL: ${url}`, `Title: ${currentData.Title}`, `InheritPermission: ${currentData.InheritPermission}(今回は権限変更なし)`, "", "--- SiteSettings差分 ---", ...diffLines, "", `新規追加項目: ${newItem ? "解体紹介料申請 (Date031, 担当:" + newItem.Description + ")" : "(見つかりません)"}`, `Columns件数 : ${(currentData.SiteSettings?.Columns || []).length} → ${modifiedSiteSettings.Columns.length}`, `Sections件数: ${(currentData.SiteSettings?.Sections || []).length} → ${modifiedSiteSettings.Sections.length}`, ].join("\n"); fs.writeFileSync(diffOutPath, diffText, "utf-8"); console.log(`\n[OK] 差分プレビューを保存しました: ${diffOutPath}`); if (!execute) { console.log("\n[注意] --execute が指定されていないため、送信は行っていません。"); console.log("内容を確認の上、問題なければ次を実行してください: node apply-to-site-496626.js --execute"); process.exit(0); } (async () => { console.log("\n実際に送信します..."); const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const text = await res.text(); const resultOutPath = path.join(modifyDir, `site-${TARGET_SITE_ID}_apply_result_${ts}.json`); fs.writeFileSync(resultOutPath, text, "utf-8"); console.log(`HTTP ${res.status} ${res.statusText}`); console.log(`[OK] 応答を保存しました: ${resultOutPath}`); try { const json = JSON.parse(text); if (json.StatusCode === 200) { console.log("[OK] 適用に成功しました。"); } else { console.error("[エラー] 適用に失敗した可能性があります。応答内容を確認してください。"); } } catch { console.log(text); } })();