157 lines
6.8 KiB
JavaScript
157 lines
6.8 KiB
JavaScript
/**
|
||
* apply-site-config.js
|
||
* ------------------------------------------------------------
|
||
* ★★★ このスクリプトは常にドライランです。実際のAPI送信は一切行いません。★★★
|
||
*
|
||
* Claudeが生成した desired_config.json(希望構成)を読み込み、
|
||
* 1. 現状構成(configs/site-{SiteId}_latest.json)との差分表示
|
||
* 2. 実際に送信されるはずの HTTPリクエスト(URL / Body)の表示
|
||
* 3. そのまま使えるcurlコマンドの出力
|
||
* のみを行います。実行(fetch送信)は行いません。
|
||
*
|
||
* 内容を確認した上で、問題なければ、
|
||
* - 表示されたcurlコマンドを手動で実行する
|
||
* - または、このファイル末尾の "実送信ブロック" のコメントを
|
||
* 自分の判断で外して実行する
|
||
* のいずれかで反映してください。
|
||
*
|
||
* desired_config.json の形式:
|
||
* {
|
||
* "Mode": "partial", // "partial" = updatesitesettings(部分更新) / "full" = updatesite(全体更新)
|
||
* "SiteId": 12345, // 省略時は config.json の値を使用
|
||
* "SiteSettings": { ... }, // 変更したい項目のみ(partialの場合)/ 全体(fullの場合)
|
||
* "Title": "...", // full更新時のみ必要な場合あり
|
||
* "ReferenceType": "...",
|
||
* "ParentId": ...,
|
||
* "InheritPermission": ...
|
||
* }
|
||
*
|
||
* 参照した公式マニュアル:
|
||
* - サイト更新(全体): https://pleasanter.org/manual/api-site-update
|
||
* URL: POST {BaseUrl}/api/items/{SiteId}/updatesite
|
||
* - サイト設定の部分更新: https://pleasanter.org/ja/manual/api-update-sitesettings
|
||
* URL: POST {BaseUrl}/api/items/{SiteId}/updatesitesettings
|
||
* ------------------------------------------------------------
|
||
*/
|
||
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
|
||
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"));
|
||
}
|
||
|
||
// ざっくりした差分表示(キー単位・トップ1階層+SiteSettings1階層まで)
|
||
// partialモード(updatesitesettings)は desired 側にあるキーだけがサーバーに送られ、
|
||
// 記載のないキーは現状のまま変わらない。そのため比較対象は afterKeys のみに限定する
|
||
// (beforeにしかないキーを「削除される」ように見せるのは誤り)。
|
||
// fullモード(updatesite)はSiteSettings全体を丸ごと置き換えるため、beforeKeysも含めて比較する。
|
||
function diffTopLevel(before, after, label, mode = "partial") {
|
||
if (!before) {
|
||
console.log(` (現状データなし。get-site-config.jsで先に取得しておくと差分表示できます)`);
|
||
return;
|
||
}
|
||
const afterKeys = new Set(Object.keys(after || {}));
|
||
const allKeys = mode === "full" ? new Set([...Object.keys(before || {}), ...afterKeys]) : afterKeys;
|
||
|
||
let changed = false;
|
||
for (const key of allKeys) {
|
||
const b = JSON.stringify(before?.[key]);
|
||
const a = JSON.stringify(after?.[key]);
|
||
if (b !== a) {
|
||
changed = true;
|
||
console.log(` [変更] ${label}.${key}`);
|
||
console.log(` - 現状: ${truncate(b)}`);
|
||
console.log(` + 希望: ${truncate(a)}`);
|
||
}
|
||
}
|
||
if (!changed) console.log(" 差分なし");
|
||
}
|
||
|
||
function truncate(str, max = 200) {
|
||
if (str === undefined) return "(undefined)";
|
||
return str.length > max ? str.slice(0, max) + "...(略)" : str;
|
||
}
|
||
|
||
function maskApiKey(key) {
|
||
if (!key) return key;
|
||
return key.length > 8 ? key.slice(0, 4) + "****" + key.slice(-4) : "****";
|
||
}
|
||
|
||
async function main() {
|
||
const baseDir = __dirname;
|
||
const configPath = path.join(baseDir, "config.json");
|
||
const desiredPath = process.argv[2] || path.join(baseDir, "desired_config.json");
|
||
|
||
const config = loadJson(configPath);
|
||
const desired = loadJson(desiredPath);
|
||
|
||
const siteId = desired.SiteId || config.SiteId;
|
||
const mode = desired.Mode || "partial";
|
||
const endpoint = mode === "full" ? "updatesite" : "updatesitesettings";
|
||
const url = `${config.BaseUrl.replace(/\/+$/, "")}/api/items/${siteId}/${endpoint}`;
|
||
|
||
const latestConfigPath = path.join(baseDir, "configs", `site-${siteId}_latest.json`);
|
||
const current = loadJson(latestConfigPath, false);
|
||
const currentSiteSettings =
|
||
current?.Response?.Data?.SiteSettings ||
|
||
current?.Response?.Site?.SiteSettings ||
|
||
current?.Response?.SiteSettings ||
|
||
null;
|
||
|
||
console.log("========================================");
|
||
console.log(" ドライラン結果(実際の送信は行いません)");
|
||
console.log("========================================");
|
||
console.log(`Mode : ${mode} (${mode === "full" ? "全体更新" : "部分更新"})`);
|
||
console.log(`URL : ${url}`);
|
||
console.log(`SiteId : ${siteId}`);
|
||
console.log("");
|
||
console.log("--- SiteSettings 差分(現状 → 希望) ---");
|
||
diffTopLevel(currentSiteSettings, desired.SiteSettings, "SiteSettings", mode);
|
||
console.log("");
|
||
|
||
const body = {
|
||
ApiVersion: config.ApiVersion || "1.1",
|
||
ApiKey: config.ApiKey,
|
||
...desired,
|
||
};
|
||
delete body.Mode; // Mode はエンドポイント選択にのみ使用し、送信Bodyには含めない
|
||
|
||
const maskedBody = { ...body, ApiKey: maskApiKey(body.ApiKey) };
|
||
console.log("--- 送信されるはずのBody(APIキーはマスク表示) ---");
|
||
console.log(JSON.stringify(maskedBody, null, 2));
|
||
console.log("");
|
||
|
||
console.log("--- 手動実行用 curl コマンド(内容確認の上、必要ならご自身で実行してください) ---");
|
||
console.log(
|
||
`curl -X POST "${url}" -H "Content-Type: application/json" -d '${JSON.stringify(body)}'`
|
||
);
|
||
console.log("");
|
||
console.log("[注意] このスクリプトはAPIへの送信を一切行っていません(ドライラン専用)。");
|
||
console.log("内容に問題がなければ、上記curlコマンドを手動実行するか、");
|
||
console.log("スクリプト内の「実送信ブロック」を有効化する改修をご自身の判断で行ってください。");
|
||
|
||
// ------------------------------------------------------------
|
||
// 実送信ブロック(デフォルトでは絶対に到達しないようにガードしています)
|
||
// 意図的に反映を自動化したい場合のみ、下のガードを外して使ってください。
|
||
// ------------------------------------------------------------
|
||
const ENABLE_ACTUAL_SEND = false; // ← 安全のため常にfalse固定。変更は自己責任で。
|
||
if (ENABLE_ACTUAL_SEND) {
|
||
const res = await fetch(url, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(body),
|
||
});
|
||
console.log(await res.text());
|
||
}
|
||
}
|
||
|
||
main();
|