ken_nogi/Pleasanter/.claude/js/extract-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

193 lines
8.0 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.

/**
* extract-site-config.js
* ------------------------------------------------------------
* 取得済みのサイト構成 (configs/site-{SiteId}_latest.json 等) から、
* Scripts / Styles / ServerScripts / Processes / ガイドHTML を
* 個別ファイルとして configs/site-{SiteId}_{サイト名}/ 配下に書き出します。
* ClaudePleasanter直下に置く共通実装。全プロジェクトの薄いラッパーから require される。
*
* 呼び出し元プロジェクトのルート・レイアウトは環境変数で受け取る:
* PLEASANTER_PROJECT_ROOT … 必須。プロジェクトルートの絶対パス
* PLEASANTER_LAYOUT … "flat" | 未設定(既定=hierarchical)
* 抽出先configs/site-{SiteId}_{サイト名}/scripts 等はlayoutによらず共通。
* layoutは生JSONlatest.jsonの探索元パスの解決にのみ影響する。
*
* 使い方:
* node extract-site-config.js
* node extract-site-config.js ./configs/site-214812_latest.json
*
* 出力:
* configs/site-{SiteId}_{サイト名}/
* manifest.json ← 抽出内容の一覧build-desired-config.jsでの再構成に使用
* scripts/{Id}_{Title}.js
* styles/{Id}_{Title}.css
* serverscripts/{Id}_{Title}.js
* guide/{GuideName}.html ← GridGuide等、ガイドサイトデータ直下、値が空でないもののみ
* html/{ColumnName}.html ← 項目ごとの補足HTMLSiteSettings.Columns[].ExtendedHtmlAfterControl
* processes.json ← プロセス設定SiteSettings.Processes をそのまま保存)
*
* 注意:
* 「guide」GridGuide等と「html」ExtendedHtmlAfterControlは別物。
* Pleasanter用語としての「Html」はColumns配下の項目ごとの補足HTMLを指し、
* GridGuide等の画面ガイド文言とは異なるフィールド。旧実装ではガイドHTMLの保存先が
* 誤って"html"フォルダになっていたため、guide/htmlに分離した2026-08-16修正
* ガイド・項目ごとの補足HTMLはいずれもSiteSettingsではなく、サイトデータ直下
* (ガイド)/Columns配下Htmlの項目です。反映時はサイト設定の部分更新
* updatesitesettingsではなく、サイト全体更新updatesite / Mode:"full")が必要になります。
* Scripts/Styles/ServerScripts/Processesは部分更新Mode:"partial")で反映できます。
* 詳しくは build-desired-config.js の実行結果メッセージを参照してください。
* ------------------------------------------------------------
*/
const fs = require("fs");
const path = require("path");
const { sanitize, ensureSiteDir, findLatestJsonPath } = require("./site-paths");
const { resolveProjectRoot, loadSiteId } = require("./resolve-project");
function getBaseDir() {
return resolveProjectRoot().baseDir;
}
function getEnv() {
return resolveProjectRoot().env;
}
function getLayout() {
const v = process.env.PLEASANTER_LAYOUT;
return v === "flat" || v === "separate" ? v : "hierarchical";
}
function ensureDir(dir) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
function extractList(items, outDir, ext, bodyKey = "Body") {
const manifestEntries = [];
if (!items || items.length === 0) return manifestEntries;
ensureDir(outDir);
for (const item of items) {
const fileName = `${item.Id}_${sanitize(item.Title || item.Name)}${ext}`;
fs.writeFileSync(path.join(outDir, fileName), item[bodyKey] || "", "utf-8");
const { [bodyKey]: _omit, ...meta } = item;
manifestEntries.push({ ...meta, File: fileName });
}
return manifestEntries;
}
const GUIDE_FIELDS = [
"GridGuide",
"EditorGuide",
"CalendarGuide",
"CrosstabGuide",
"GanttGuide",
"BurnDownGuide",
"TimeSeriesGuide",
"AnalyGuide",
"KambanGuide",
"ImageLibGuide",
];
// get-site-config.js からも呼び出せるよう関数化。
// CLI実行時直接 `node extract-site-config.js`)は末尾のブロックが引数解決を行う。
function extractSiteConfig(latestPath) {
const baseDir = getBaseDir();
if (!fs.existsSync(latestPath)) {
console.error(`[エラー] ファイルが見つかりません: ${latestPath}`);
console.error("先に node get-site-config.js を実行してください。");
process.exit(1);
}
const json = JSON.parse(fs.readFileSync(latestPath, "utf-8"));
const data = json?.Response?.Data;
if (!data) {
console.error("[エラー] Response.Data が見つかりません。getsiteのレスポンス形式を確認してください。");
process.exit(1);
}
const siteId = data.SiteId;
const settings = data.SiteSettings || {};
const configsDir = path.join(baseDir, "configs", getEnv());
const outRoot = ensureSiteDir(configsDir, siteId, data.Title);
const manifest = {
SiteId: siteId,
Title: data.Title,
ExtractedAt: new Date().toISOString(),
SourceFile: path.relative(baseDir, latestPath).replace(/\\/g, "/"),
};
manifest.Scripts = extractList(settings.Scripts, path.join(outRoot, "scripts"), ".js");
manifest.Styles = extractList(settings.Styles, path.join(outRoot, "styles"), ".css");
manifest.ServerScripts = extractList(settings.ServerScripts, path.join(outRoot, "serverscripts"), ".js");
// ガイドサイトデータ直下。SiteSettingsではない点に注意
const guideDir = path.join(outRoot, "guide");
const guideEntries = [];
for (const field of GUIDE_FIELDS) {
const value = data[field];
if (value) {
ensureDir(guideDir);
const fileName = `${field}.html`;
fs.writeFileSync(path.join(guideDir, fileName), value, "utf-8");
guideEntries.push({ Field: field, File: fileName, TopLevel: true });
}
}
manifest.Guide = guideEntries;
// Html項目ごとの補足HTML。SiteSettings.Columns[].ExtendedHtmlAfterControl
const htmlDir = path.join(outRoot, "html");
const htmlEntries = [];
for (const col of settings.Columns || []) {
const value = col.ExtendedHtmlAfterControl;
if (value) {
ensureDir(htmlDir);
const fileName = `${sanitize(col.ColumnName)}.html`;
fs.writeFileSync(path.join(htmlDir, fileName), value, "utf-8");
htmlEntries.push({ ColumnName: col.ColumnName, File: fileName });
}
}
manifest.Html = htmlEntries;
// プロセス設定 … 個別分割せず1つの定義ファイルとして保存
fs.writeFileSync(
path.join(outRoot, "processes.json"),
JSON.stringify(settings.Processes || [], null, 2),
"utf-8"
);
manifest.ProcessesFile = "processes.json";
manifest.ProcessesCount = (settings.Processes || []).length;
fs.writeFileSync(path.join(outRoot, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
console.log(`[OK] 抽出完了: ${outRoot}`);
console.log(` Scripts : ${manifest.Scripts.length}`);
console.log(` Styles : ${manifest.Styles.length}`);
console.log(` ServerScripts : ${manifest.ServerScripts.length}`);
console.log(` Guide : ${manifest.Guide.length}`);
console.log(` Html : ${manifest.Html.length}`);
console.log(` Processes : ${manifest.ProcessesCount}件 → processes.json`);
console.log("\n次のステップ: 各ファイルを直接編集するか、Claudeに修正内容を指示してください。");
console.log(`修正後は次のコマンドで反映用JSONを作成できます: node build-desired-config.js ${path.basename(outRoot)}`);
return manifest;
}
// CLIとして直接実行された場合のみ、引数からlatestPathを解決して実行する
if (require.main === module) {
const { baseDir, env, argv } = resolveProjectRoot();
let latestPath = argv[0];
if (!latestPath) {
const siteId = loadSiteId(baseDir);
const layout = getLayout();
const configsDir = path.join(baseDir, "configs", env);
const resolved = findLatestJsonPath(baseDir, configsDir, siteId, layout, env);
latestPath = resolved || path.join(configsDir, `site-${siteId}_latest.json`);
}
extractSiteConfig(latestPath);
}
module.exports = { extractSiteConfig };