const fs = require("fs"); const path = require("path"); const child_process = require("child_process"); const ROOT = __dirname; const CONFIG_PATH = path.join(ROOT, "config.json"); const CONFIGS_DIR = path.join(ROOT, "configs"); const DOCS_DIR = path.join(ROOT, "docs"); const SITESETTING_JSON_DIR = path.join(ROOT, "siteSettingJsons"); 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"], "参照先の列(From)": ["From", "FromColumn", "FromField"], "自サイトへのコピー先(To)": ["To", "ToColumn", "ToField"], "上書き設定": ["Overwrite"], "実行条件(Current→Changed)": ["Condition"], "動作": ["Action"], }; 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 buildColumnLabelMap(columns) { const map = {}; getArray(columns).forEach((col) => { const name = col.ColumnName || col.Name || col.Field; if (name) map[name] = col.LabelText || col.DisplayName || col.Title || ""; }); return map; } function parsePermissionEntry(perm) { if (typeof perm === "string") { const [type, target, level] = perm.split(","); return { Type: type || "", Target: target || "", Level: level || "" }; } return { 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), }; } function resolveGridColumnEntry(col, labelMap) { if (typeof col === "string") { return { Column: col, Label: labelMap[col] || "" }; } const name = formatValue(col.ColumnName || col.Column || col.Name || col.Field); return { Column: name, Label: formatValue(col.LabelText || col.DisplayName || col.Title || labelMap[name] || "") }; } function objectToFieldPermissionRows(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return []; return Object.entries(value).map(([field, level]) => ({ Field: field, Permission: formatValue(level), })); } function formatValue(value) { if (value === null || value === undefined) return ""; if (typeof value === "boolean") return value ? "true" : "false"; if (Array.isArray(value)) { return value.map((item) => (item !== null && typeof item === "object" ? JSON.stringify(item) : String(item))).join(", "); } if (typeof value === "object") return JSON.stringify(value); 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 searchDirs = [SITESETTING_JSON_DIR, CONFIGS_DIR]; for (const dir of searchDirs) { if (!fs.existsSync(dir)) continue; const candidate = path.join(dir, `site-${siteId}_latest.json`); if (fs.existsSync(candidate)) return candidate; const all = fs.readdirSync(dir).filter((name) => name.startsWith(`site-${siteId}_`) && name.endsWith(".json")); if (all.length > 0) return path.join(dir, all[0]); } return 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, siteJsonPath) { const title = data.Title || `Site ${siteId}`; const specLines = []; const siteJsonRelPath = path.relative(DOCS_DIR, siteJsonPath).replace(/\\/g, "/"); specLines.push(`# サイト仕様書:${title}(SiteId: ${siteId})\n`); specLines.push(`- 取得元データ: [${path.basename(siteJsonPath)}](${encodeURI(siteJsonRelPath)})`); 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 = getArray(settings?.Permissions).length > 0 ? getArray(settings.Permissions) : getArray(data.Permissions); specLines.push(`## 2. アクセス権限(Permissions)`); if (permissions.length > 0) { const permRows = permissions.map((perm) => { const { Type, Target, Level } = parsePermissionEntry(perm); return { Type, Target, Level }; }); specLines.push(formatMarkdownTable(["種別", "対象", "権限値"], permRows)); specLines.push(`※ 権限値はビットフラグの加算方式のため、正確な意味は環境のロール定義に依存します。`); } else if (String(data.InheritPermission) !== String(siteId)) { specLines.push(`このサイト自体には権限設定がなく、SiteId ${formatValue(data.InheritPermission)} の権限を継承しています。継承元サイトの権限設定を別途確認してください。`); } else { specLines.push(`権限設定なし。`); } specLines.push(`## 3. 画面構成`); const columnLabelMap = buildColumnLabelMap(settings?.Columns || []); const gridColumns = settings?.GridColumns || []; if (gridColumns.length > 0) { specLines.push(`### 3.1 一覧画面(GridColumns)`); const gridRows = gridColumns.map((col, index) => { const { Column, Label } = resolveGridColumnEntry(col, columnLabelMap); return { No: index + 1, Column, Label }; }); 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) { Object.keys(editorHash).forEach((tabKey) => { specLines.push(`#### タブ: ${tabKey}`); const itemRows = getArray(editorHash[tabKey]).map((item, index) => { if (typeof item === "string" && /^_Section-\d+/.test(item)) { const secId = item.match(/^_Section-(\d+)/)[1]; const section = sections.find((s) => String(s.Id) === secId); return { No: index + 1, Column: item, Label: `―(セクション「${section?.LabelText || ""}」開始)` }; } const { Column, Label } = resolveGridColumnEntry(item, columnLabelMap); return { No: index + 1, Column, Label }; }); specLines.push(formatMarkdownTable(["順番", "列名(内部)", "表示ラベル"], itemRows)); }); } 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 項目ごとの補足HTML(ExtendedHtmlAfterControl)`); 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 createRows = objectToFieldPermissionRows(settings?.PermissionForCreating); const updateRows = objectToFieldPermissionRows(settings?.PermissionForUpdating); specLines.push(`## 5. 作成・更新権限(フィールド単位)`); if (createRows.length > 0 || updateRows.length > 0) { if (createRows.length > 0) { specLines.push(`### PermissionForCreating`); specLines.push(formatMarkdownTable(["項目", "権限設定"], createRows)); } if (updateRows.length > 0) { specLines.push(`### PermissionForUpdating`); specLines.push(formatMarkdownTable(["項目", "権限設定"], updateRows)); } } else { specLines.push(`設定なし。`); } 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 / ルックアップ)`); links.forEach((link) => { const targetSite = formatValue(link.SiteId ?? link.TargetSiteId ?? link.ReferenceSiteId); const targetLabel = link.TableName && Number(link.SiteId) === 0 ? `マスタテーブル: ${link.TableName}` : `参照先SiteId: ${targetSite}`; specLines.push(`### 参照列: ${formatValue(link.ColumnName)}(${targetLabel})`); const lookups = getArray(link.Lookups); if (lookups.length > 0) { const lookupRows = lookups.map((lk) => ({ From: formatValue(lk.From || lk.FromColumn || lk.FromField), To: formatValue(lk.To || lk.ToColumn || lk.ToField), Overwrite: formatValue(lk.OverwriteForm ?? lk.Overwrite ?? ""), })); specLines.push(formatMarkdownTable(["参照先の列(From)", "自サイトへのコピー先(To)", "上書き設定"], lookupRows)); } if (link.SearchFormat) specLines.push(`- 検索表示フォーマット: \`${link.SearchFormat}\``); specLines.push(`- JsonFormat: ${formatValue(link.JsonFormat)}`); }); } const processes = manifest ? readJson(path.join(findExtractionDir(siteId), "processes.json")) : settings?.Processes || []; if (processes.length > 0) { specLines.push(`## 8. プロセス設定(Processes)`); const processesRelPath = path.relative(DOCS_DIR, path.join(findExtractionDir(siteId), "processes.json")).replace(/\\/g, "/"); specLines.push(`- 定義ファイル: [${processesRelPath}](${encodeURI(processesRelPath)})`); 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.DataChanges ? "データ変更(DataChanges)" : proc.ValidateInputs ? "入力必須チェック(ValidateInputs)" : proc.Name)), })); specLines.push(formatMarkdownTable(["Id", "Name", "DisplayName", "実行条件(Current→Changed)", "動作"], procRows)); } function relLinkFromDocs(subdir, fileName) { const abs = path.join(findExtractionDir(siteId), subdir, fileName); const rel = path.relative(DOCS_DIR, abs).replace(/\\/g, "/"); return `[${fileName}](${encodeURI(rel)})`; } if (manifest?.Styles?.length > 0) { specLines.push(`## 9. スタイル(Styles)`); const styleRows = manifest.Styles.map((style) => ({ Id: formatValue(style.Id), Title: formatValue(style.Title), File: relLinkFromDocs("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: relLinkFromDocs("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: relLinkFromDocs("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 = []; const columns = getArray(settings?.Columns || []).filter((col) => col.LabelText || col.DisplayName || col.Title); const processes = manifest ? readJson(path.join(findExtractionDir(siteId), "processes.json")) : settings?.Processes || []; const links = getArray(settings?.Links || []); const guideText = [data.GridGuide, data.EditorGuide, data.CalendarGuide, data.GanttGuide, data.CrosstabGuide, data.TimeSeriesGuide, data.BurnDownGuide, data.AnalyGuide, data.KambanGuide].filter(Boolean).join("\n"); overviewLines.push(`# 概要書:${title}`); overviewLines.push(`- この資料は業務担当者向けに、システム上の構成をもとに「何を管理し、どのような流れで使うか」を整理したものです。`); overviewLines.push(`- 技術仕様の詳細は [./site-${siteId}_${sanitizedTitle}_spec.md](./site-${siteId}_${sanitizedTitle}_spec.md) を参照してください。\n`); overviewLines.push(`## 1. これはどんな画面か`); overviewLines.push(`このサイトは「${title}」を管理するための業務画面です。${data.ReferenceType || "Results"}として扱われ、登録・照会・更新の基本的な業務フローに沿って利用されます。`); if (guideText) { overviewLines.push(`- 画面上の案内では、${guideText.replace(/\n/g, " ").slice(0, 240)}${guideText.length > 240 ? "..." : ""}`); } if (columns.length > 0) { overviewLines.push(`## 2. 主に入力・参照する情報`); const importantFields = columns.filter((col) => { const name = String(col.ColumnName || col.Name || ""); const label = String(col.LabelText || col.DisplayName || col.Title || ""); return /Status|ClassA|ClassB|ClassC|ClassD|ClassE|ClassF|ClassG|ClassH|ClassI|ClassJ|ClassK|ClassM|ClassN|ClassO|ClassZ|Date|Description|Attachments|Title/i.test(name + label); }); const sourceFields = importantFields.length > 0 ? importantFields : columns; const items = sourceFields.slice(0, 12).map((col) => ({ Field: formatValue(col.LabelText || col.DisplayName || col.Title || col.ColumnName || col.Name), Purpose: inferOverviewPurpose(col), })); overviewLines.push(formatMarkdownTable(["項目", "用途"], items)); } if (processes.length > 0) { overviewLines.push(`## 3. 業務でよく行う操作`); const flowRows = processes.slice(0, 12).map((proc) => ({ Operation: formatValue(proc.Name || proc.DisplayName || "プロセス"), Change: formatValue(proc.CurrentStatus || "") + (proc.ChangedStatus ? ` → ${proc.ChangedStatus}` : ""), Description: inferProcessMeaning(proc), })); overviewLines.push(formatMarkdownTable(["操作", "状態の変化", "説明"], flowRows)); } if (links.length > 0) { overviewLines.push(`## 4. 他の管理表とのつながり`); links.forEach((link) => { const targetSite = formatValue(link.SiteId || link.TargetSiteId || link.ReferenceSiteId || "関連サイト"); const desc = inferLinkMeaning(link); overviewLines.push(`- ${targetSite} の情報を参照・反映する連携です。${desc}`); }); } overviewLines.push(`## 5. 利用時のポイント`); overviewLines.push(`- まずは画面上の入力項目を確認して、必要な情報を登録します。`); overviewLines.push(`- 進捗や状態はステータス項目で管理し、必要に応じて他サイトの情報を参照します。`); overviewLines.push(`- 重要な更新は履歴や添付情報と合わせて確認し、引き継ぎや問い合わせに備えます。`); return { markdown: overviewLines.join("\n\n"), sanitizedTitle }; } function inferOverviewPurpose(column) { const name = String(column.ColumnName || column.Name || ""); const label = String(column.LabelText || column.DisplayName || column.Title || ""); const text = `${name}${label}`; if (/Status/i.test(text)) return "登録状況や進捗を管理する項目です。"; if (/ClassA|管理番号|管理/i.test(text)) return "案件や物件を一意に識別するための項目です。"; if (/ClassB|登録者|担当|担当者/i.test(text)) return "誰が登録・担当しているかを管理する項目です。"; if (/Date|日時|日付/i.test(text)) return "予定や実施日などの時点を管理する項目です。"; if (/Description|備考|内容|コメント/i.test(text)) return "補足情報や業務メモを入力する項目です。"; if (/Attachments|添付/i.test(text)) return "関連書類や証跡を添付するための項目です。"; if (/Title/i.test(text)) return "件名や対象名を入力する項目です。"; if (/Class[0-9]{3}|Class[0-9]{2,3}/i.test(text)) return "業務上の分類コードを入力する項目です。"; return "業務上の情報を入力・保持するための項目です。"; } function inferProcessMeaning(proc) { const name = String(proc.Name || proc.DisplayName || ""); const current = String(proc.CurrentStatus || ""); const changed = String(proc.ChangedStatus || ""); const text = `${name}${current}${changed}`; if (/承認|Approve|approval/i.test(text)) return "承認・確認の流れを進める操作です。"; if (/保留|Pending|hold/i.test(text)) return "一時停止や後続作業への待ち状態を作る操作です。"; if (/取消|Cancel|cancel/i.test(text)) return "登録内容を取り消す操作です。"; if (/終了|End|complete|完了/i.test(text)) return "業務を完了状態に遷移させる操作です。"; if (/登録|Create|new/i.test(text)) return "新規登録の初期状態を作る操作です。"; if (/チェック|Check/i.test(text)) return "入力内容や整合性を確認する操作です。"; return "業務状態を遷移させるための操作です。"; } function inferLinkMeaning(link) { const from = String(link.From || link.FromColumn || link.FromField || ""); const to = String(link.To || link.ToColumn || link.ToField || ""); if (from && to) return `${from} と ${to} の対応を取り、関連情報として参照します。`; return "関連するマスタや別サイトの情報を参照する連携です。"; } function resolveRequestedSiteIds(config) { const cliEntries = process.argv.slice(2); if (cliEntries.length > 0) { return cliEntries.flatMap((entry) => parseSiteIds(entry)); } return parseSiteIds(config.SiteId); } function generateAllDocs() { if (!fs.existsSync(CONFIG_PATH)) { console.error(`[エラー] 設定ファイルが見つかりません: ${CONFIG_PATH}`); process.exit(1); } const config = readJson(CONFIG_PATH); const siteIds = resolveRequestedSiteIds(config); if (siteIds.length === 0) { console.error("[エラー] config.json の SiteId から有効なサイトIDを取得できませんでした。"); process.exit(1); } ensureDir(CONFIGS_DIR); ensureDir(DOCS_DIR); console.log("[INFO] まず既存サイト情報を取得・抽出します..."); try { child_process.execFileSync("node", [path.join(ROOT, "get-site-config.js")], { stdio: "inherit", cwd: ROOT, }); } 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, siteJsonPath); const titleToken = sanitizeName(data.Title || `site-${siteId}`); const specPath = path.join(DOCS_DIR, `site-${siteId}_${titleToken}_spec.md`); const legacySpecPath = path.join(CONFIGS_DIR, `site-${siteId}_spec.md`); fs.writeFileSync(specPath, specMarkdown, "utf-8"); fs.writeFileSync(legacySpecPath, 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(ROOT, "md-to-pdf.js"), overviewPath], { stdio: "inherit", cwd: ROOT, }); 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(); }