ken_nogi/Pleasanter/.claude/js/site-scripts/★マスターシート/apply-to-site-488755-production.js
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。
NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは
別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter
インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:37:06 +09:00

182 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* apply-to-site-488755-production.js
* ------------------------------------------------------------
* 488755「【開発中】★建売マスターシート」の staging環境nextoffice2.next-hd.net
* 検証済みのSiteSettingsを、production環境nextoffice.next-hd.co.jpの同一SiteId 488755へ
* 丸ごと適用する改修4ステップ原則のステップ4: production反映
*
* 前提確認実行前、2026-08-31:
* - production側488755は実データ1件のみテスト用の空レコード、最終更新2026-06-06
* 業務運用は実質されていない。SiteSettings丸ごと置き換えの実害は極めて小さいと判断。
* - Title/Permissions4件は staging/production で完全一致を確認済み。
*
* 送信直前に staging/production 双方をAPIから直接再取得するローカルconfigsキャッシュの
* 同期漏れによる巻き戻り事故を防ぐため。189112 CheckH復元時の教訓
* Title/ReferenceType/ParentId/InheritPermission/Permissions は production側の現状値を維持し、
* SiteSettings のみ staging側の内容で丸ごと置き換える。
*
* 使い方:
* node apply-to-site-488755-production.js … 差分表示・プレビュー保存のみ(送信なし)
* node apply-to-site-488755-production.js --execute … 上記に加えて実際にproductionへ送信する
* ------------------------------------------------------------
*/
const fs = require("fs");
const path = require("path");
const { findSiteDir, newModifyDir } = require("../../site-paths");
const { REPO_ROOT, loadServerConfig } = require("../../resolve-project");
const PROJECT_NAME = "★マスターシート";
const SITE_ID = 488755;
const SOURCE_ENV = "staging";
const TARGET_ENV = "production";
const baseDir = path.join(REPO_ROOT, PROJECT_NAME);
const execute = process.argv.includes("--execute");
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, "-");
}
async function getSite(env, siteId) {
const config = loadServerConfig(env);
const url = `${config.BaseUrl.replace(/\/+$/, "")}/api/items/${siteId}/getsite`;
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ApiVersion: config.ApiVersion || "1.1", ApiKey: config.ApiKey }),
});
const json = await res.json();
if (json.StatusCode !== 200) {
console.error(`[エラー] ${env}のgetsiteに失敗しました:`, JSON.stringify(json).slice(0, 500));
process.exit(1);
}
return { config, data: json.Response.Data };
}
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;
}
(async () => {
console.log("========================================");
console.log(` 488755 production反映staging → production${execute ? "(実行)" : "(プレビューのみ)"}`);
console.log("========================================");
console.log(`\n[INFO] ${SOURCE_ENV}環境からSiteId ${SITE_ID} を取得中...`);
const { data: sourceData } = await getSite(SOURCE_ENV, SITE_ID);
console.log(`[OK] 取得しましたVer: ${sourceData.Ver}, UpdatedTime: ${sourceData.UpdatedTime}`);
console.log(`\n[INFO] ${TARGET_ENV}環境からSiteId ${SITE_ID} を取得中...`);
const { config: targetConfig, data: targetData } = await getSite(TARGET_ENV, SITE_ID);
console.log(`[OK] 取得しましたVer: ${targetData.Ver}, UpdatedTime: ${targetData.UpdatedTime}`);
// Permissions・Title等の整合性チェック想定外の食い違いがあれば警告し停止する
const titleMatch = sourceData.Title === targetData.Title;
const permMatch = JSON.stringify(sourceData.Permissions) === JSON.stringify(targetData.Permissions);
console.log(`\nTitle一致: ${titleMatch ? "OK" : "[警告] 不一致 staging=" + sourceData.Title + " / production=" + targetData.Title}`);
console.log(`Permissions一致: ${permMatch ? "OK" : "[警告] 不一致"}`);
const modifiedSiteSettings = sourceData.SiteSettings;
const url = `${targetConfig.BaseUrl.replace(/\/+$/, "")}/api/items/${SITE_ID}/updatesite`;
const body = {
ApiVersion: targetConfig.ApiVersion || "1.1",
ApiKey: targetConfig.ApiKey,
SiteId: SITE_ID,
Title: targetData.Title,
ReferenceType: targetData.ReferenceType,
ParentId: targetData.ParentId,
InheritPermission: targetData.InheritPermission,
Permissions: targetData.Permissions,
SiteSettings: modifiedSiteSettings,
};
console.log(`\nURL : ${url}`);
console.log(`Title : ${targetData.Title}production現状のまま維持`);
console.log(`InheritPermission: ${targetData.InheritPermission}production現状のまま維持`);
console.log("");
console.log("--- SiteSettings 差分production現状 → staging由来の反映後 ---");
const diffLines = diffTopLevel(targetData.SiteSettings, modifiedSiteSettings);
diffLines.forEach((l) => console.log(l));
console.log("");
console.log(`Columns件数 : ${(targetData.SiteSettings?.Columns || []).length}${modifiedSiteSettings.Columns.length}`);
console.log(`Sections件数 : ${(targetData.SiteSettings?.Sections || []).length}${modifiedSiteSettings.Sections.length}`);
console.log(`Scripts件数 : ${(targetData.SiteSettings?.Scripts || []).length}${modifiedSiteSettings.Scripts.length}`);
console.log(`Processes件数: ${(targetData.SiteSettings?.Processes || []).length}${modifiedSiteSettings.Processes.length}`);
console.log(`Links件数 : ${(targetData.SiteSettings?.Links || []).length}${modifiedSiteSettings.Links.length}`);
const configsDir = path.join(baseDir, "configs", TARGET_ENV);
const targetSiteDir = findSiteDir(configsDir, SITE_ID);
const ts = timestamp();
if (targetSiteDir) {
const modifyDir = newModifyDir(targetSiteDir, "staging反映_production");
fs.writeFileSync(
path.join(modifyDir, `before_site-${SITE_ID}_production_${ts}.json`),
JSON.stringify({ StatusCode: 200, Response: { Data: targetData } }, null, 2),
"utf-8"
);
const maskedBody = { ...body, ApiKey: maskApiKey(body.ApiKey) };
fs.writeFileSync(
path.join(modifyDir, `site-${SITE_ID}_preview_${ts}.json`),
JSON.stringify(maskedBody, null, 2),
"utf-8"
);
const diffText = [
`488755 production反映 差分プレビュー (${ts})`,
`URL: ${url}`,
`Title: ${targetData.Title}`,
"",
"--- SiteSettings差分 ---",
...diffLines,
"",
`Columns件数 : ${(targetData.SiteSettings?.Columns || []).length}${modifiedSiteSettings.Columns.length}`,
`Sections件数 : ${(targetData.SiteSettings?.Sections || []).length}${modifiedSiteSettings.Sections.length}`,
`Scripts件数 : ${(targetData.SiteSettings?.Scripts || []).length}${modifiedSiteSettings.Scripts.length}`,
`Processes件数: ${(targetData.SiteSettings?.Processes || []).length}${modifiedSiteSettings.Processes.length}`,
].join("\n");
fs.writeFileSync(path.join(modifyDir, `site-${SITE_ID}_apply_diff_${ts}.txt`), diffText, "utf-8");
console.log(`\n[OK] 変更前スナップショット・プレビュー・差分を保存しました: ${modifyDir}`);
}
if (!execute) {
console.log("\n[注意] --execute が指定されていないため、送信は行っていません。");
console.log("内容を確認の上、問題なければ次を実行してください: node apply-to-site-488755-production.js --execute");
process.exit(0);
}
console.log("\n実際にproduction環境へ送信しますupdatesite...");
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const text = await res.text();
console.log(`HTTP ${res.status} ${res.statusText}`);
console.log(text);
if (targetSiteDir) {
const modifyDir = newModifyDir(targetSiteDir, "staging反映_production");
fs.writeFileSync(path.join(modifyDir, `site-${SITE_ID}_apply_result_${ts}.json`), text, "utf-8");
}
})();