/** * extract-site-config.js * ------------------------------------------------------------ * 取得済みのサイト構成 (configs/site-{SiteId}_latest.json) から、 * Scripts / Styles / ServerScripts / Processes / ガイドHTML を * 個別ファイルとして configs/site-{SiteId}/ 配下に書き出します。 * * 使い方: * 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 * html/{GuideName}.html ← GridGuide等、ガイドHTML(値が空でないもののみ) * processes.json ← プロセス設定(SiteSettings.Processes をそのまま保存) * * 注意: * ガイドHTML(GridGuide等)はSiteSettingsではなく、サイトデータ直下の項目です。 * そのため反映時はサイト設定の部分更新(updatesitesettings)ではなく、 * サイト全体更新(updatesite / Mode:"full")が必要になります。 * Scripts/Styles/ServerScripts/Processesは部分更新(Mode:"partial")で反映できます。 * 詳しくは build-desired-config.js の実行結果メッセージを参照してください。 * ------------------------------------------------------------ */ const fs = require("fs"); const path = require("path"); function sanitize(name) { return ( String(name || "untitled") .replace(/[\\/:*?"<>|]/g, "_") .trim() .slice(0, 60) || "untitled" ); } 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; } // config.json の SiteId(数値 / カンマ区切り文字列 / 配列)を // 個々のSiteId文字列の配列に正規化する(get-site-config.jsと同仕様)。 function parseSiteIds(rawSiteId) { const list = Array.isArray(rawSiteId) ? rawSiteId : String(rawSiteId).split(","); return list .map((id) => String(id).trim()) .filter((id) => id.length > 0); } 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 = __dirname; 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 dirName = `site-${siteId}_${sanitize(data.Title)}`; const outRoot = path.join(baseDir, "configs", dirName); ensureDir(outRoot); 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"); // ガイドHTML(サイトデータ直下。SiteSettingsではない点に注意) const htmlDir = path.join(outRoot, "html"); const htmlEntries = []; for (const field of GUIDE_FIELDS) { const value = data[field]; if (value) { ensureDir(htmlDir); const fileName = `${field}.html`; fs.writeFileSync(path.join(htmlDir, fileName), value, "utf-8"); htmlEntries.push({ Field: field, File: fileName, TopLevel: true }); } } 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(` Html(ガイド) : ${manifest.Html.length}件`); console.log(` Processes : ${manifest.ProcessesCount}件 → processes.json`); console.log("\n次のステップ: 各ファイルを直接編集するか、Claudeに修正内容を指示してください。"); console.log(`修正後は次のコマンドで反映用JSONを作成できます: node build-desired-config.js ${dirName}`); return manifest; } // CLIとして直接実行された場合のみ、引数からlatestPathを解決して実行する if (require.main === module) { const baseDir = __dirname; const latestPathArg = process.argv[2]; if (latestPathArg) { extractSiteConfig(latestPathArg); } else { const configPath = path.join(baseDir, "config.json"); if (!fs.existsSync(configPath)) { console.error("[エラー] config.jsonが見つかりません。抽出対象のJSONパスを引数で指定してください。"); process.exit(1); } const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); const siteIds = parseSiteIds(config.SiteId); if (siteIds.length === 0) { console.error("[エラー] config.json の SiteId から有効なサイトIDを取得できませんでした。"); process.exit(1); } const missing = []; for (const siteId of siteIds) { const latestPath = path.join(baseDir, "configs", `site-${siteId}_latest.json`); if (!fs.existsSync(latestPath)) { console.error(`[警告] ファイルが見つかりません(スキップ): ${latestPath}`); missing.push(siteId); continue; } if (siteIds.length > 1) { console.log(`\n==== SiteId: ${siteId} ====`); } extractSiteConfig(latestPath); } if (missing.length > 0) { console.error(`\n[エラー] 未取得のSiteIdがあります: ${missing.join(", ")}`); console.error("先に node get-site-config.js を実行してください。"); process.exit(1); } } } module.exports = { extractSiteConfig };