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>
182 lines
8.6 KiB
JavaScript
182 lines
8.6 KiB
JavaScript
/**
|
||
* 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/Permissions(4件)は 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");
|
||
}
|
||
})();
|