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

160 lines
7.3 KiB
JavaScript
Raw Permalink 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.

/**
* site-paths.js
* ------------------------------------------------------------
* configs/ 配下のフォルダ構成を一箇所で管理する共有ヘルパー。
* ClaudePleasanter直下に置く共通実装。全プロジェクトの .claude/js/ 配下
* (またはプロジェクト直下)にある薄いラッパーから require される。
*
* baseDirプロジェクトルートは各スクリプトの main() が
* process.env.PLEASANTER_PROJECT_ROOTラッパーが設定から受け取る。
* このファイル自体はbaseDir解決を行わず、呼び出し側から渡されたconfigsDirを
* そのまま使うlayout非依存の純粋なパス組み立てヘルパー
*
* レイアウトは2種類:
* hierarchical既定: configs/site-{SiteId}_{サイト名}/sitesettings/ に生JSONを保存
* flat : configs/ 直下に site-{SiteId}_latest.json をフラットに保存
* (抽出済みファイル configs/site-{SiteId}_{サイト名}/scripts/ 等は
* レイアウトによらず共通)
* layoutは各プロジェクトのラッパーが PLEASANTER_LAYOUT 環境変数("flat"|未設定=hierarchical
* で明示指定する。既存configsフォルダの実際の保存構造を変えないための切り替えであり、
* 自動判定はしない。
*
* 構成hierarchicalの場合:
* configs/
* master/ … ユーザー/組織/グループのマスターデータget-master-data.js
* site-{SiteId}_{サイト名}/
* sitesettings/ … getsiteの生JSONsite-{SiteId}_latest.json / _{timestamp}.json
* scripts/ styles/ serverscripts/ html/ processes.json / manifest.json
* … extract-site-config.js による分割済みファイル(従来通り)
* docs/ … 仕様書(.md)や改善要望プラン等、sitesettings以外の関連ドキュメント
* modify/{request}/ … 修正依頼ごとに新規作成。変更前後のconfigファイルや差分・結果を保管
* ------------------------------------------------------------
*/
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 });
return dir;
}
// configs/ 配下から "site-{siteId}_*"(前方一致)または "site-{siteId}"(完全一致)の
// フォルダを探す。見つからなければ null。抽出済みファイルの置き場所はlayoutによらず共通
function findSiteDir(configsDir, siteId) {
if (!fs.existsSync(configsDir)) return null;
const bare = `site-${siteId}`;
const prefix = `site-${siteId}_`;
const candidates = fs.readdirSync(configsDir).filter((name) => {
const full = path.join(configsDir, name);
return fs.statSync(full).isDirectory() && (name === bare || name.startsWith(prefix));
});
const suffixed = candidates.filter((n) => n !== bare);
if (suffixed.length >= 1) return path.join(configsDir, suffixed[0]);
if (candidates.includes(bare)) return path.join(configsDir, bare);
return null;
}
// 既存フォルダがあればそれを使い、無ければ site-{siteId}_{sanitize(title)} を新規作成する
function ensureSiteDir(configsDir, siteId, title) {
const existing = findSiteDir(configsDir, siteId);
if (existing) return existing;
const dir = path.join(configsDir, `site-${siteId}_${sanitize(title)}`);
return ensureDir(dir);
}
function sitesettingsDir(siteDir) {
return ensureDir(path.join(siteDir, "sitesettings"));
}
function docsDir(siteDir) {
return ensureDir(path.join(siteDir, "docs"));
}
// 修正依頼用の新規フォルダを作成するlabelを省略した場合はタイムスタンプを使う
function newModifyDir(siteDir, label) {
const name = label || new Date().toISOString().replace(/[:.]/g, "-");
return ensureDir(path.join(siteDir, "modify", name));
}
// 既存のmodifyフォルダの中で最も新しいものフォルダ名の降順ソートを返す。無ければnull。
function latestModifyDir(siteDir) {
const modifyRoot = path.join(siteDir, "modify");
if (!fs.existsSync(modifyRoot)) return null;
const dirs = fs
.readdirSync(modifyRoot)
.filter((n) => fs.statSync(path.join(modifyRoot, n)).isDirectory())
.sort();
if (dirs.length === 0) return null;
return path.join(modifyRoot, dirs[dirs.length - 1]);
}
// サイトの最新getsiteダンプsite-{siteId}_latest.jsonのパスを返すhierarchicalレイアウト用
// フォルダ自体が無い場合は null。
function latestJsonPath(configsDir, siteId) {
const siteDir = findSiteDir(configsDir, siteId);
if (!siteDir) return null;
return path.join(siteDir, "sitesettings", `site-${siteId}_latest.json`);
}
// 生JSONgetsiteダンプの置き場所は、プロジェクトごとに3パターンある
// 既存configsフォルダの実際の保存構造を変えないための切り替え。自動判定はしない:
// hierarchical既定: configs/site-{siteId}_{title}/sitesettings/
// flat : configs/ 直下にフラット保存(サイト名フォルダを作らない)
// separate : baseDir/siteSettingJsons/ 直下にフラット保存configs/とは別のトップレベルフォルダ)
// 抽出済みファイルscripts/styles/等)の置き場所 configs/site-{siteId}_{title}/ 自体は
// どのlayoutでも共通extract-site-config.js等はconfigsDirをそのまま使えばよい
// 生JSON保存先ディレクトリを解決する保存前。フォルダが無ければ作成する
// baseDir: プロジェクトルート。configsDir: baseDir/configs/{env}呼び出し元でenvを織り込み済み
// env: "production"|"development"。hierarchical/flatはconfigsDir側で分離済みのため未使用、
// separateのみ baseDir/siteSettingJsons/{env}/ として直接envを使うconfigsDirを経由しないため
function rawJsonSaveDir(baseDir, configsDir, siteId, title, layout, env) {
if (layout === "flat") {
return ensureDir(configsDir);
}
if (layout === "separate") {
return ensureDir(path.join(baseDir, "siteSettingJsons", env));
}
const siteDir = ensureSiteDir(configsDir, siteId, title);
return sitesettingsDir(siteDir);
}
// 既存サイトの生JSON latestファイルパスを layout に応じて解決する参照用、無ければnull
function findLatestJsonPath(baseDir, configsDir, siteId, layout, env) {
if (layout === "flat") {
const p = path.join(configsDir, `site-${siteId}_latest.json`);
return fs.existsSync(p) ? p : null;
}
if (layout === "separate") {
const p = path.join(baseDir, "siteSettingJsons", env, `site-${siteId}_latest.json`);
return fs.existsSync(p) ? p : null;
}
return latestJsonPath(configsDir, siteId);
}
module.exports = {
sanitize,
ensureDir,
findSiteDir,
ensureSiteDir,
sitesettingsDir,
docsDir,
newModifyDir,
latestModifyDir,
latestJsonPath,
rawJsonSaveDir,
findLatestJsonPath,
};