ken_nogi/Pleasanter/.claude/js/generate-site-documentation.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

537 lines
26 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.

/**
* generate-site-documentation.js
* ------------------------------------------------------------
* 取得済みのサイト構成から、技術仕様書docs/site-{SiteId}_spec.md
* 業務担当者向け概要書docs/site-{SiteId}_{サイト名}_overview.md、PDF化込み
* 生成する。ClaudePleasanter直下に置く共通実装。
* ClaudePleasanter直下から直接実行する対象プロジェクトは --project で指定)。
*
* 権限PermissionsのInheritPermission考慮やグリッド列ラベル解決など、より
* 詳細な出力が必要な場合は generate-site-documentation-full.js を使用してください。
*
* 使い方:
* node .claude/js/generate-site-documentation.js --project="新・稟議申請システム" --env=production
* node .claude/js/generate-site-documentation.js --project=新・稟議申請システム --env=production 319015 319016
* ------------------------------------------------------------
*/
const fs = require("fs");
const path = require("path");
const child_process = require("child_process");
const { resolveProjectRoot, REPO_ROOT, loadSiteId } = require("./resolve-project");
let ROOT, ENV, CONFIGS_DIR, DOCS_DIR;
function ensureDir(dir) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
}
function sanitizeName(raw) {
return String(raw || "").trim()
.replace(/[\\/:*?"<>|]/g, "_")
.replace(/\s+/g, " ")
.replace(/\.+$/, "")
.slice(0, 60)
.trim();
}
function parseSiteIds(rawSiteId) {
const list = Array.isArray(rawSiteId) ? rawSiteId : String(rawSiteId).split(",");
return list.map((id) => String(id).trim()).filter((id) => id.length > 0);
}
function resolveSiteData(json) {
if (json?.Response?.Data) return json.Response.Data;
if (json?.Response?.Site?.Data) return json.Response.Site.Data;
if (json?.Response?.Data?.SiteSettings) return json.Response.Data;
if (json?.Response?.SiteSettings) return json.Response;
throw new Error("getsiteレスポンスの想定構造が見つかりません。Response.Data を含むJSONを渡してください。");
}
function resolveTableCellValue(row, header) {
if (row[header] != null) return row[header];
const normalized = String(header)
.replace(/\s+/g, "")
.replace(/[]/g, "")
.replace(//g, "/");
if (row[normalized] != null) return row[normalized];
const aliasMap = {
"順番": ["No", "Order", "Index"],
"列名": ["Column", "ColumnName", "Field", "Name"],
"列名(内部)": ["Column", "ColumnName", "Field", "Name"],
"表示ラベル": ["Label", "LabelText", "DisplayName", "Title"],
"説明/入力ガイド": ["Description", "DescriptionText", "ToolTip", "InputHelpText"],
"備考": ["Remarks", "Remark", "Notes"],
"種別": ["Type", "Kind"],
"対象": ["Target", "TargetId"],
"権限値": ["Permission", "PermissionValue", "Level", "Value"],
"タブ/キー": ["Tab", "Key", "TabKey"],
"項目一覧": ["Items", "ItemList"],
"項目": ["Field", "Column", "ColumnName"],
"権限設定": ["Permission", "PermissionSetting", "Value"],
"ファイル": ["File", "Path"],
"要約": ["Summary", "Description"],
"操作": ["Operation", "Name"],
"状態の変化": ["Change", "Transition"],
"説明": ["Description", "Detail"],
"起点列": ["From", "FromColumn", "FromField"],
"参照先SiteId": ["TargetSite", "TargetSiteId", "SiteId"],
"参照先の値→コピー先": ["To", "ToColumn", "ToField"],
"JsonFormat": ["JsonFormat", "Format"],
"ラベル": ["Label", "LabelText"],
"内容": ["Content", "Value", "Body"],
};
for (const alias of aliasMap[header] || []) {
if (row[alias] != null) return row[alias];
}
return "";
}
function formatMarkdownTable(header, rows) {
if (!rows || rows.length === 0) return "";
const heads = header.map((h) => `| ${h} `).join("") + "|";
const sep = header.map(() => "| --- ").join("") + "|";
const body = rows
.map((row) => {
return header
.map((cell) => {
const value = resolveTableCellValue(row, cell);
return `| ${String(value).replace(/\|/g, "\\|")} `;
})
.join("") + "|";
})
.join("\n");
return `${heads}\n${sep}\n${body}\n`;
}
function inferColumnType(column) {
const name = String(column.ColumnName || column.Name || "");
if (/^(Class[A-Z]|Class\d{3}|Class)/.test(name)) return "文字列";
if (/^Num/.test(name)) return "数値";
if (/^Date/.test(name)) return "日付";
if (/^Description/.test(name)) return "説明";
if (/^Check/.test(name)) return "チェック";
if (/^Attachments?/.test(name)) return "添付";
if (/^(Owner|Manager|Assignee|CreatedBy|UpdatedBy|Updator|Users?)/.test(name)) return "ユーザー";
if (/^Status$/i.test(name)) return "状態";
if (/Id$/.test(name) && !/^Title$/i.test(name)) return "ID";
if (/Title/i.test(name)) return "タイトル";
return column.Type || column.FieldType || "不明";
}
function getArray(value) {
if (!value) return [];
return Array.isArray(value) ? value : [value];
}
function formatValue(value) {
if (value === null || value === undefined) return "";
if (typeof value === "boolean") return value ? "true" : "false";
if (Array.isArray(value)) return value.join(", ");
return String(value);
}
function findExtractionDir(siteId) {
if (!fs.existsSync(CONFIGS_DIR)) return null;
const candidates = fs.readdirSync(CONFIGS_DIR).filter((name) => {
const full = path.join(CONFIGS_DIR, name);
return fs.statSync(full).isDirectory() && name.startsWith(`site-${siteId}`);
});
return candidates[0] ? path.join(CONFIGS_DIR, candidates[0]) : null;
}
function loadManifest(siteId) {
const dir = findExtractionDir(siteId);
if (!dir) return null;
const manifestPath = path.join(dir, "manifest.json");
if (!fs.existsSync(manifestPath)) return null;
return readJson(manifestPath);
}
function getSiteJsonPath(siteId) {
const candidate = path.join(CONFIGS_DIR, `site-${siteId}_latest.json`);
if (fs.existsSync(candidate)) return candidate;
const all = fs.readdirSync(CONFIGS_DIR).filter((name) => name.startsWith(`site-${siteId}_`) && name.endsWith(".json"));
return all.length > 0 ? path.join(CONFIGS_DIR, all[0]) : null;
}
function buildBasicInfoTable(data, settings) {
const rows = [
{ Key: "TenantId", Value: formatValue(data.TenantId) },
{ Key: "Title", Value: formatValue(data.Title) },
{ Key: "ReferenceType", Value: formatValue(data.ReferenceType) },
{ Key: "ParentId", Value: formatValue(data.ParentId) },
{ Key: "InheritPermission", Value: formatValue(data.InheritPermission) },
{ Key: "Publish", Value: formatValue(data.Publish) },
{ Key: "DisableCrossSearch", Value: formatValue(data.DisableCrossSearch) },
{ Key: "Creator", Value: formatValue(data.Creator) },
{ Key: "Updator", Value: formatValue(data.Updator || data.UpdatedBy) },
{ Key: "CreatedTime", Value: formatValue(data.CreatedTime) },
{ Key: "UpdatedTime", Value: formatValue(data.UpdatedTime) },
{ Key: "SiteSettings.Version", Value: formatValue(settings?.Version) },
];
return rows.map((row) => `| ${row.Key} | ${row.Value} |`).join("\n") + "\n";
}
function inferLinkDescription(link) {
const from = link.From || link.FromColumn || link.FromField || "(不明)";
const to = link.To || link.ToColumn || link.ToField || "(不明)";
return `${from}${to}`;
}
function scriptSummary(filePath) {
if (!fs.existsSync(filePath)) return "";
const content = fs.readFileSync(filePath, "utf-8");
const line = content.split(/\r?\n/).find((l) => l.trim().length > 0);
return line ? line.slice(0, 120) : "";
}
function generateSpecMarkdown(siteId, data, settings, manifest) {
const title = data.Title || `Site ${siteId}`;
const specLines = [];
specLines.push(`# サイト仕様書:${title}SiteId: ${siteId}\n`);
specLines.push(`- 取得元データ: [site-${siteId}_latest.json](./site-${siteId}_latest.json)`);
specLines.push(`- 取得日時: ${formatValue(data.UpdatedTime || data.CreatedTime || "不明")}`);
specLines.push(`- 設定バージョン: ${formatValue(settings?.Version || "不明")}\n`);
specLines.push(`## 1. サイト基本情報`);
specLines.push("| 項目 | 値 |\n| --- | --- |\n" + buildBasicInfoTable(data, settings));
const permissions = settings?.Permissions || data.Permissions || [];
if (permissions.length > 0) {
specLines.push(`## 2. アクセス権限Permissions`);
const permRows = permissions.map((perm) => ({
Type: formatValue(perm.Type || perm.PermissionType || perm.AuthorityType),
Target: formatValue(perm.TargetId || perm.Target || perm.RoleId || perm.Value),
Level: formatValue(perm.Permission || perm.Value || perm.Level),
}));
specLines.push(formatMarkdownTable(["種別", "対象", "権限値"], permRows));
specLines.push(`※ 権限値は環境のロール定義に依存します。`);
}
specLines.push(`## 3. 画面構成`);
const gridColumns = settings?.GridColumns || [];
if (gridColumns.length > 0) {
specLines.push(`### 3.1 一覧画面GridColumns`);
const gridRows = gridColumns.map((col, index) => ({
No: index + 1,
Column: formatValue(col.ColumnName || col.Column || col.Name || col.Field),
Label: formatValue(col.LabelText || col.DisplayName || col.Title || ""),
}));
specLines.push(formatMarkdownTable(["順番", "列名", "表示ラベル"], gridRows));
}
const editorHash = settings?.EditorColumnHash || {};
const sections = settings?.Sections || [];
if (Object.keys(editorHash).length > 0 || sections.length > 0) {
specLines.push(`### 3.2 編集画面レイアウトEditorColumnHash / Sections`);
if (Object.keys(editorHash).length > 0) {
const editorRows = Object.keys(editorHash).map((tabKey) => ({
Tab: tabKey,
Items: formatValue(getArray(editorHash[tabKey]).join(", ")),
}));
specLines.push(formatMarkdownTable(["タブ/キー", "項目一覧"], editorRows));
}
if (sections.length > 0) {
const sectionRows = sections.map((section) => ({
Id: formatValue(section.Id),
Label: formatValue(section.LabelText || section.Name || ""),
AllowExpand: formatValue(section.AllowExpand),
Expand: formatValue(section.Expand),
}));
specLines.push(`\n#### Sections`);
specLines.push(formatMarkdownTable(["Id", "ラベル", "AllowExpand", "Expand"], sectionRows));
}
}
const titleColumns = settings?.TitleColumns || [];
if (titleColumns.length > 0) {
specLines.push(`### 3.3 タイトル表示TitleColumns / TitleSeparator`);
specLines.push(`- タイトル項目: ${formatValue(titleColumns.join(" + "))}`);
specLines.push(`- 区切り文字: ${formatValue(settings?.TitleSeparator || "")}`);
}
const columns = settings?.Columns || [];
if (columns.length > 0) {
specLines.push(`## 4. 項目定義Columns`);
const colRows = columns.map((col) => ({
Column: formatValue(col.ColumnName || col.Name || col.Field || col.Id),
Type: inferColumnType(col),
Label: formatValue(col.LabelText || col.DisplayName || col.Title || ""),
Description: formatValue(col.Description || col.ToolTip || col.InputHelpText || ""),
Remarks: [col.NoWrap ? "NoWrap" : null, col.FieldCss ? "FieldCss" : null, col.ExtendedHtmlAfterControl ? "ExtendedHtmlAfterControl" : null].filter(Boolean).join(", "),
}));
specLines.push(formatMarkdownTable(["列名(内部)", "種別", "表示ラベル", "説明/入力ガイド", "備考"], colRows));
const extendedItems = columns.filter((col) => col.ExtendedHtmlAfterControl);
if (extendedItems.length > 0) {
specLines.push(`## 4.1 項目ごとの補足HTMLExtendedHtmlAfterControl`);
const extRows = extendedItems.map((col) => ({
Column: formatValue(col.ColumnName || col.Name || col.Field),
Label: formatValue(col.LabelText || col.DisplayName || col.Title || ""),
Content: formatValue(col.ExtendedHtmlAfterControl),
}));
specLines.push(formatMarkdownTable(["列名", "ラベル", "内容"], extRows));
}
}
const createPerm = settings?.PermissionForCreating || [];
const updatePerm = settings?.PermissionForUpdating || [];
if (createPerm.length > 0 || updatePerm.length > 0) {
specLines.push(`## 5. 作成・更新権限(フィールド単位)`);
if (createPerm.length > 0) {
specLines.push(`### PermissionForCreating`);
const createRows = createPerm.map((item) => ({
Field: formatValue(item.ColumnName || item.Field || item.Column),
Permission: formatValue(item.Required || item.Permission || item.Value),
}));
specLines.push(formatMarkdownTable(["項目", "権限設定"], createRows));
}
if (updatePerm.length > 0) {
specLines.push(`### PermissionForUpdating`);
const updateRows = updatePerm.map((item) => ({
Field: formatValue(item.ColumnName || item.Field || item.Column),
Permission: formatValue(item.Required || item.Permission || item.Value),
}));
specLines.push(formatMarkdownTable(["項目", "権限設定"], updateRows));
}
}
const aggregations = settings?.Aggregations || [];
if (aggregations.length > 0) {
specLines.push(`## 6. 集計設定Aggregations`);
const aggRows = aggregations.map((item) => ({
Id: formatValue(item.Id),
GroupBy: formatValue(item.GroupBy),
Type: formatValue(item.Type),
Target: formatValue(item.Target),
}));
specLines.push(formatMarkdownTable(["Id", "GroupBy", "Type", "Target"], aggRows));
}
const links = settings?.Links || [];
if (links.length > 0) {
specLines.push(`## 7. 他サイト連携Links / ルックアップ)`);
const linkRows = links.map((link) => ({
From: formatValue(link.From || link.FromColumn || link.FromField),
TargetSite: formatValue(link.SiteId || link.TargetSiteId || link.ReferenceSiteId),
To: formatValue(link.To || link.ToColumn || link.ToField),
JsonFormat: formatValue(link.JsonFormat || link.Format),
}));
specLines.push(formatMarkdownTable(["起点列", "参照先SiteId", "参照先の値→コピー先", "JsonFormat"], linkRows));
}
const processes = manifest ? readJson(path.join(findExtractionDir(siteId), "processes.json")) : settings?.Processes || [];
if (processes.length > 0) {
specLines.push(`## 8. プロセス設定Processes`);
specLines.push(`- 定義ファイル: [configs/${path.relative(ROOT, path.join(findExtractionDir(siteId), "processes.json")).replace(/\\/g, "/")}]`);
const procRows = processes.map((proc) => ({
Id: formatValue(proc.Id),
Name: formatValue(proc.Name),
DisplayName: formatValue(proc.DisplayName || proc.Name),
Condition: formatValue(`${proc.CurrentStatus || ""}${proc.ChangedStatus || ""}`),
Action: formatValue(proc.OnClick || proc.Action || proc.Name),
}));
specLines.push(formatMarkdownTable(["Id", "Name", "DisplayName", "実行条件(Current→Changed)", "動作"], procRows));
}
if (manifest?.Styles?.length > 0) {
specLines.push(`## 9. スタイルStyles`);
const styleRows = manifest.Styles.map((style) => ({
Id: formatValue(style.Id),
Title: formatValue(style.Title),
File: `[${style.File}](./${path.posix.join(path.basename(findExtractionDir(siteId)), "styles", style.File)})`,
Remarks: formatValue(style.Disabled ? "Disabled" : ""),
}));
specLines.push(formatMarkdownTable(["Id", "Title", "ファイル", "備考"], styleRows));
}
const scripts = manifest?.Scripts || [];
const serverScripts = manifest?.ServerScripts || [];
if (scripts.length > 0 || serverScripts.length > 0) {
specLines.push(`## 10. スクリプトScriptsサーバースクリプトServerScripts`);
if (scripts.length > 0) {
const scriptRows = scripts.map((script) => ({
Id: formatValue(script.Id),
Title: formatValue(script.Title),
File: `[${script.File}](./${path.posix.join(path.basename(findExtractionDir(siteId)), "scripts", script.File)})`,
Summary: formatValue(scriptSummary(path.join(findExtractionDir(siteId), "scripts", script.File))),
}));
specLines.push(`### Scripts`);
specLines.push(formatMarkdownTable(["Id", "Title", "ファイル", "要約"], scriptRows));
}
if (serverScripts.length > 0) {
const serverRows = serverScripts.map((script) => ({
Id: formatValue(script.Id),
Title: formatValue(script.Title),
File: `[${script.File}](./${path.posix.join(path.basename(findExtractionDir(siteId)), "serverscripts", script.File)})`,
Summary: formatValue(scriptSummary(path.join(findExtractionDir(siteId), "serverscripts", script.File))),
}));
specLines.push(`### ServerScripts`);
specLines.push(formatMarkdownTable(["Id", "Title", "ファイル", "要約"], serverRows));
}
}
const otherSettings = { ...settings };
["Permissions", "GridColumns", "EditorColumnHash", "Sections", "TitleColumns", "TitleSeparator", "Columns", "PermissionForCreating", "PermissionForUpdating", "Aggregations", "Links", "Processes", "Scripts", "Styles", "ServerScripts"].forEach((k) => delete otherSettings[k]);
const otherEntries = Object.entries(otherSettings).filter(([key, value]) => value !== null && value !== undefined && value !== "" && !(Array.isArray(value) && value.length === 0) && !(typeof value === "object" && Object.keys(value).length === 0));
if (otherEntries.length > 0) {
specLines.push(`## 11. その他設定`);
specLines.push(`| 設定キー | 値 |\n| --- | --- |`);
otherEntries.forEach(([key, value]) => {
specLines.push(`| ${key} | ${formatValue(value)} |`);
});
}
const alertRows = [];
if (settings?.Scripts?.some((script) => /API_KEY|SECRET|Bearer\s+|token|webhook|password/i.test(script.Body || ""))) {
alertRows.push("抽出されたスクリプト内に API キーやシークレット、Webhook URL、Bearerトークンなどの機密情報が含まれている可能性があります。内容を目視で確認してください。");
}
if (alertRows.length > 0) {
specLines.push(`## 気になる点(レビュー観点)`);
alertRows.forEach((line) => specLines.push(`- ${line}`));
}
return specLines.join("\n\n");
}
function generateOverviewMarkdown(siteId, data, settings, manifest) {
const title = data.Title || `Site ${siteId}`;
const sanitizedTitle = sanitizeName(title);
const overviewLines = [];
overviewLines.push(`# 概要書:${title}`);
overviewLines.push(`- 技術仕様(項目のシステム上の名称・設定値等)は [../configs/site-${siteId}_spec.md](../configs/site-${siteId}_spec.md) を参照してください。`);
overviewLines.push(`- 本書は業務担当者向けに機能面のみを平易に説明するものです。\n`);
overviewLines.push(`## 1. これは何か`);
overviewLines.push(`このサイトは「${title}」で、${formatValue(data.ReferenceType || "データ")}を管理・参照するための画面です。`);
const columns = getArray(settings?.Columns || []).filter((col) => col.LabelText || col.DisplayName || col.Title);
if (columns.length > 0) {
overviewLines.push(`## 2. 登場する情報(入力項目)`);
const items = columns.map((col) => ({
Category: inferColumnType(col),
Field: formatValue(col.LabelText || col.DisplayName || col.Title),
Notes: col.Hide ? "非表示項目" : formatValue(col.Description || col.ToolTip || ""),
}));
overviewLines.push(formatMarkdownTable(["分類", "項目", "内容"], items));
}
const processes = manifest ? readJson(path.join(findExtractionDir(siteId), "processes.json")) : settings?.Processes || [];
if (processes.length > 0) {
overviewLines.push(`## 3. 業務の流れ(ボタン操作)`);
const flowRows = processes.map((proc) => ({
Operation: formatValue(proc.Name || proc.DisplayName || "プロセス"),
Change: formatValue(proc.CurrentStatus || "") + (proc.ChangedStatus ? `${proc.ChangedStatus}` : ""),
Description: formatValue(proc.Description || proc.DisplayName || `状態を変更するプロセスです。`),
}));
overviewLines.push(formatMarkdownTable(["操作", "状態の変化", "説明"], flowRows));
}
const notifications = getArray(settings?.Notifications || data.Notifications || []);
if (notifications.length > 0) {
overviewLines.push(`## 4. 通知される場面`);
notifications.forEach((noti) => {
const who = formatValue(noti.To || noti.Target || noti.Recipient || noti.Role);
const when = formatValue(noti.When || noti.Trigger || noti.Condition || "不明");
const what = formatValue(noti.Message || noti.Subject || noti.Template || "通知が送信されます。");
overviewLines.push(`- ${when}${who} へ通知: ${what}`);
});
}
const links = getArray(settings?.Links || []);
if (links.length > 0) {
overviewLines.push(`## 5. 関連する仕組み(マスタ連携)`);
links.forEach((link) => {
const desc = inferLinkDescription(link);
overviewLines.push(`- ${formatValue(link.Description || link.Title || "関連マスタ連携")}: ${desc}`);
});
}
if (columns.length === 0 && processes.length === 0 && notifications.length === 0 && links.length === 0) {
overviewLines.push(`## 5. 関連する仕組み(マスタ連携)`);
overviewLines.push(`- このサイトは現時点では項目・プロセス・通知・他サイト連携の情報が限定的です。`);
}
return { markdown: overviewLines.join("\n\n"), sanitizedTitle };
}
function generateAllDocs() {
const resolved = resolveProjectRoot();
ROOT = resolved.baseDir;
ENV = resolved.env;
CONFIGS_DIR = path.join(ROOT, "configs", ENV);
DOCS_DIR = path.join(ROOT, "docs");
const siteIds = resolved.argv.length > 0 ? resolved.argv.flatMap(parseSiteIds) : parseSiteIds(loadSiteId(ROOT));
if (siteIds.length === 0) {
console.error("[エラー] siteid.json の SiteId から有効なサイトIDを取得できませんでした。");
process.exit(1);
}
ensureDir(CONFIGS_DIR);
ensureDir(DOCS_DIR);
console.log("[INFO] まず既存サイト情報を取得・抽出します...");
try {
child_process.execFileSync("node", [path.join(REPO_ROOT, ".claude", "js", "get-site-config.js"), `--project=${ROOT}`, `--env=${ENV}`], {
stdio: "inherit",
});
} catch (err) {
console.error("[エラー] get-site-config.js の実行に失敗しました。取得処理を確認してください。");
process.exit(1);
}
for (const siteId of siteIds) {
console.log(`\n==== ドキュメント生成: SiteId=${siteId} ==== `);
const siteJsonPath = getSiteJsonPath(siteId);
if (!siteJsonPath) {
console.error(`[警告] site-${siteId}_latest.json が見つかりません。スキップします。`);
continue;
}
const json = readJson(siteJsonPath);
let data;
try {
data = resolveSiteData(json);
} catch (err) {
console.error(`[エラー] ${siteJsonPath} のデータ構造を解釈できませんでした: ${err.message}`);
continue;
}
const settings = data.SiteSettings || {};
const manifest = loadManifest(siteId);
const specMarkdown = generateSpecMarkdown(siteId, data, settings, manifest);
const specPath = path.join(CONFIGS_DIR, `site-${siteId}_spec.md`);
fs.writeFileSync(specPath, specMarkdown, "utf-8");
console.log(`[OK] 仕様書生成: ${specPath}`);
const { markdown: overviewMarkdown, sanitizedTitle } = generateOverviewMarkdown(siteId, data, settings, manifest);
const overviewName = `site-${siteId}_${sanitizedTitle}_overview.md`;
const overviewPath = path.join(DOCS_DIR, overviewName);
fs.writeFileSync(overviewPath, overviewMarkdown, "utf-8");
console.log(`[OK] 概要書生成: ${overviewPath}`);
try {
child_process.execFileSync("node", [path.join(REPO_ROOT, ".claude", "js", "md-to-pdf.js"), overviewPath], {
stdio: "inherit",
});
console.log(`[OK] PDF生成完了: ${overviewPath.replace(/\.md$/i, ".pdf")}`);
} catch (err) {
console.error(`[エラー] md-to-pdf.js による PDF 生成に失敗しました: ${overviewPath}`);
console.error(err.message || err);
}
}
}
if (require.main === module) {
generateAllDocs();
}