ken_nogi/Pleasanter/.claude/js/get-site-config.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

205 lines
7.9 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.

/**
* get-site-config.js
* ------------------------------------------------------------
* プリザンターの指定サイトの構成情報SiteSettings等を取得し、
* {プロジェクトフォルダ}/configs/{env}/ 配下にタイムスタンプ付きJSONとして保存します。
* ClaudePleasanter直下に置く共通実装。ClaudePleasanter直下から直接実行する
* (対象プロジェクトは --project、サーバー環境は --env で指定。詳細は resolve-project.js 参照)。
*
* 使い方:
* node .claude/js/get-site-config.js --project="IC(東京)【引継依頼】" --env=production
* node .claude/js/get-site-config.js --project=IC東京 --env=development
*
* レイアウトconfigs/{env}/の保存構造)は環境変数で切り替え可能:
* PLEASANTER_LAYOUT … "flat" | 未設定(既定=hierarchical)
*
* 事前準備:
* 1. リポジトリルート直下の config_production.json / config_development.json に
* BaseUrl / ApiKey / ApiVersionサーバー接続情報が設定済みであること。
* 2. 対象プロジェクトフォルダ直下の siteid.json に SiteId を設定してください。
* SiteId はカンマ区切りで複数指定可能です(例: "480111,438166,96235")。
* 1件のみの場合は従来通り数値例: 480111でも構いません。
* 3. Node.js 18以降組み込みfetchを使用
*
* 参照した公式マニュアル:
* - 開発者向け機能APIサイト操作サイト更新
* https://pleasanter.org/manual/api-site-update
* - 開発者向け機能APIサイト操作サイト設定の更新部分追加/更新/削除)
* https://pleasanter.org/ja/manual/api-update-sitesettings
*
* 注意:
* プリザンターのバージョンにより get のレスポンス構造
* SiteSettingsが Response.Site 配下にあるか等)が異なる場合があります。
* 初回実行時はコンソールに出力されるレスポンスのトップレベルキーを確認し、
* 想定通りの構造か確認してください。
* ------------------------------------------------------------
*/
const fs = require("fs");
const path = require("path");
const { extractSiteConfig } = require("./extract-site-config");
const { rawJsonSaveDir } = require("./site-paths");
const { resolveProjectRoot, loadServerConfig, loadSiteId } = require("./resolve-project");
let _resolved = null;
function getResolved() {
if (!_resolved) _resolved = resolveProjectRoot();
return _resolved;
}
function getBaseDir() {
return getResolved().baseDir;
}
function getEnv() {
return getResolved().env;
}
function getLayout() {
const v = process.env.PLEASANTER_LAYOUT;
return v === "flat" || v === "separate" ? v : "hierarchical";
}
// config.json の SiteId数値 / カンマ区切り文字列 / 配列)を
// 個々のSiteId文字列の配列に正規化する。
function parseSiteIds(rawSiteId) {
const list = Array.isArray(rawSiteId) ? rawSiteId : String(rawSiteId).split(",");
return list
.map((id) => String(id).trim())
.filter((id) => id.length > 0);
}
// 1サイト分の取得・保存・抽出処理
async function fetchAndSaveSite(siteId, { BaseUrl, ApiKey, ApiVersion }) {
const url = `${BaseUrl.replace(/\/+$/, "")}/api/items/${siteId}/getsite`;
const body = {
ApiVersion: ApiVersion || "1.1",
ApiKey: ApiKey,
};
console.log(`[INFO] 取得先: ${url}`);
console.log(`[INFO] SiteId: ${siteId}`);
let response;
try {
response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
} catch (err) {
console.error("[エラー] リクエスト送信に失敗しました:", err.message);
console.error("BaseUrlの疎通・ネットワーク設定を確認してください。");
return { ok: false };
}
const text = await response.text();
if (!response.ok) {
console.error(`[エラー] HTTP ${response.status} ${response.statusText}`);
console.error(text);
return { ok: false };
}
let json;
try {
json = JSON.parse(text);
} catch (err) {
console.error("[エラー] レスポンスがJSONとして解析できませんでした。");
console.error(text);
return { ok: false };
}
// レスポンス構造の確認用ログ(初回はここを見て構造を把握してください)
console.log("[INFO] レスポンス トップレベルキー:", Object.keys(json));
if (json.Response) {
console.log("[INFO] Response配下のキー:", Object.keys(json.Response));
}
const baseDir = getBaseDir();
const env = getEnv();
const layout = getLayout();
const configsDir = path.join(baseDir, "configs", env);
const title = json?.Response?.Data?.Title;
const outDir = rawJsonSaveDir(baseDir, configsDir, siteId, title, layout, env);
const timestamp = new Date()
.toISOString()
.replace(/[:.]/g, "-");
const outFile = path.join(outDir, `site-${siteId}_${timestamp}.json`);
fs.writeFileSync(outFile, JSON.stringify(json, null, 2), "utf-8");
// 常に「最新」ファイルも別名で保持Claudeへのアップロード用に固定名があると便利
const latestFile = path.join(outDir, `site-${siteId}_latest.json`);
fs.writeFileSync(latestFile, JSON.stringify(json, null, 2), "utf-8");
console.log(`[OK] 保存しました: ${outFile}`);
console.log(`[OK] 最新版としても保存しました: ${latestFile}`);
// 取得と同時に Scripts/Styles/ServerScripts/Processes/ガイドHTML も抽出する
console.log("\n[INFO] 続けて設定の個別ファイル抽出を実行します...");
extractSiteConfig(latestFile);
// 権限継承先の追跡: InheritPermission が自サイト以外を指している場合、
// 実際の権限はそちらのサイトにあるため、そのサイトも同様に取得する。
const inheritPermission = json?.Response?.Data?.InheritPermission;
return { ok: true, inheritPermission };
}
async function main() {
const { baseDir } = getResolved();
const env = getEnv();
const { BaseUrl, ApiKey, ApiVersion } = loadServerConfig(env);
const SiteId = loadSiteId(baseDir);
const siteIds = parseSiteIds(SiteId);
if (siteIds.length === 0) {
console.error("[エラー] config.json の SiteId から有効なサイトIDを取得できませんでした。");
process.exit(1);
}
// 権限継承先の追跡: 指定サイトの取得後、InheritPermission が自サイト以外を
// 指していれば、そのサイトも同じ要領でキューに追加して取得する
// (権限の実体は継承元サイド側にあるため)。
const queue = [...siteIds];
const visited = new Set();
const results = [];
while (queue.length > 0) {
const siteId = String(queue.shift());
if (visited.has(siteId)) continue;
visited.add(siteId);
console.log(`\n==== [${results.length + 1}] SiteId: ${siteId} ====`);
const { ok, inheritPermission } = await fetchAndSaveSite(siteId, { BaseUrl, ApiKey, ApiVersion });
results.push({ siteId, ok });
if (
ok &&
inheritPermission != null &&
String(inheritPermission) !== "0" &&
String(inheritPermission) !== siteId &&
!visited.has(String(inheritPermission))
) {
console.log(`[INFO] SiteId ${siteId} は権限をSiteId ${inheritPermission} から継承しています。継承元も取得します。`);
queue.push(String(inheritPermission));
}
}
console.log("\n==== 取得結果まとめ ====");
for (const { siteId, ok } of results) {
console.log(` SiteId ${siteId}: ${ok ? "OK" : "失敗"}`);
}
console.log("\n次のステップ: latest.jsonをClaudeのチャットにアップロードして仕様書化を依頼するか、");
console.log("configs/site-{SiteId}/ 配下のファイルを直接編集・指示して修正してください。");
if (results.some((r) => !r.ok)) {
process.exit(1);
}
}
main();