@skspwork/config-doc 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skspwork/config-doc",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Interactive documentation tool for JSON configuration files (appsettings.json)",
5
5
  "keywords": [
6
6
  "config",
@@ -31,7 +31,7 @@
31
31
  "packages/cli/bin",
32
32
  "packages/cli/package.json",
33
33
  "packages/web/app",
34
- "packages/web/components",
34
+ "packages/web/components",
35
35
  "packages/web/lib",
36
36
  "packages/web/types",
37
37
  "packages/web/public",
@@ -1,6 +1,7 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
2
  import path from 'path';
3
3
  import { FileSystemService } from '@/lib/fileSystem';
4
+ import { StorageService } from '@/lib/storage';
4
5
  import { ProjectConfigFiles } from '@/types';
5
6
  import { getRootPath } from '@/lib/getRootPath';
6
7
 
@@ -8,8 +9,32 @@ export async function GET() {
8
9
  try {
9
10
  const rootPath = getRootPath();
10
11
  const fsService = new FileSystemService(rootPath);
12
+ const storageService = new StorageService(fsService);
11
13
 
12
- const metadata = await fsService.loadConfigFiles();
14
+ // project_settings.jsonを読み込み
15
+ const settings = await fsService.loadProjectSettings();
16
+
17
+ if (!settings) {
18
+ return NextResponse.json({
19
+ success: true,
20
+ data: null
21
+ });
22
+ }
23
+
24
+ // UI用にConfigFileInfo形式に変換(互換性のため)
25
+ const configFiles = settings.configFiles.map((filePath, index) => ({
26
+ id: `config-${index + 1}`,
27
+ fileName: filePath.split(/[/\\]/).pop() || 'config.json',
28
+ filePath,
29
+ docsFileName: storageService.getDocsFileName(filePath)
30
+ }));
31
+
32
+ const metadata: ProjectConfigFiles = {
33
+ projectName: settings.projectName,
34
+ createdAt: '', // 不要になったが互換性のため
35
+ lastModified: new Date().toISOString(),
36
+ configFiles
37
+ };
13
38
 
14
39
  return NextResponse.json({
15
40
  success: true,
@@ -41,50 +66,46 @@ export async function POST(request: NextRequest) {
41
66
 
42
67
  const rootPath = getRootPath();
43
68
  const fsService = new FileSystemService(rootPath);
69
+ const storageService = new StorageService(fsService);
44
70
 
45
- // メタデータを作成または更新
46
- const existingMetadata = await fsService.loadConfigFiles();
71
+ // 既存の設定を読み込み
72
+ const existingSettings = await fsService.loadProjectSettings();
47
73
 
48
- // 削除された設定ファイルの docs.json を削除
74
+ // 削除されたファイルのdocsを削除
49
75
  const deletedDocsFileNames: string[] = [];
50
- if (existingMetadata && existingMetadata.configFiles) {
51
- const newFilePaths = new Set(configFilePaths);
52
- for (const oldConfig of existingMetadata.configFiles) {
53
- if (!newFilePaths.has(oldConfig.filePath)) {
54
- // この設定ファイルは削除された
55
- deletedDocsFileNames.push(oldConfig.docsFileName);
56
- await fsService.deleteConfigDocs(oldConfig.docsFileName);
76
+ if (existingSettings && existingSettings.configFiles) {
77
+ const newPaths = new Set(
78
+ configFilePaths.map((p: string) =>
79
+ path.isAbsolute(p) ? path.relative(rootPath, p) : p
80
+ )
81
+ );
82
+
83
+ for (const oldPath of existingSettings.configFiles) {
84
+ if (!newPaths.has(oldPath)) {
85
+ const docsFileName = storageService.getDocsFileName(oldPath);
86
+ deletedDocsFileNames.push(docsFileName);
87
+ await fsService.deleteConfigDocs(docsFileName);
57
88
  }
58
89
  }
59
90
  }
60
91
 
61
- const metadata: ProjectConfigFiles = {
62
- projectName: existingMetadata?.projectName || path.basename(rootPath),
63
- createdAt: existingMetadata?.createdAt || new Date().toISOString(),
64
- lastModified: new Date().toISOString(),
65
- configFiles: configFilePaths.map((filePath, index) => {
66
- const fileName = filePath.split(/[/\\]/).pop() || 'config.json';
67
- const docsFileName = fileName.replace('.json', '.docs.json');
68
-
69
- // 絶対パスの場合は相対パスに変換
70
- const relativePath = path.isAbsolute(filePath)
71
- ? path.relative(rootPath, filePath)
72
- : filePath;
73
-
74
- return {
75
- id: `config-${index + 1}`,
76
- fileName,
77
- filePath: relativePath,
78
- docsFileName
79
- };
80
- })
92
+ // 絶対パスを相対パスに変換
93
+ const relativePaths = configFilePaths.map((p: string) =>
94
+ path.isAbsolute(p) ? path.relative(rootPath, p) : p
95
+ );
96
+
97
+ // 新しい設定を保存
98
+ const newSettings = {
99
+ projectName: existingSettings?.projectName || path.basename(rootPath),
100
+ configFiles: relativePaths,
101
+ export: existingSettings?.export || {}
81
102
  };
82
103
 
83
- await fsService.saveConfigFiles(metadata);
104
+ await fsService.saveProjectSettings(newSettings);
84
105
 
85
106
  return NextResponse.json({
86
107
  success: true,
87
- message: 'メタデータを保存しました',
108
+ message: 'プロジェクト設定を保存しました',
88
109
  deletedDocsFiles: deletedDocsFileNames
89
110
  });
90
111
  } catch (error) {
@@ -3,17 +3,17 @@ import { getRootPath } from '@/lib/getRootPath';
3
3
  import path from 'path';
4
4
  import fs from 'fs/promises';
5
5
  import { ExportSettings, UserSettings, ProjectSettings } from '@/types';
6
+ import { FileSystemService } from '@/lib/fileSystem';
6
7
 
7
- const USER_SETTINGS_FILE = '.user.local.json';
8
- const PROJECT_SETTINGS_FILE = 'settings.json';
8
+ const USER_SETTINGS_FILE = '.user_settings.json';
9
9
  const CONFIG_DOC_DIR = '.config_doc';
10
10
 
11
11
  // GET: エクスポート設定を読み込み
12
12
  export async function GET() {
13
13
  try {
14
14
  const rootPath = getRootPath();
15
+ const fsService = new FileSystemService(rootPath);
15
16
  const userSettingsPath = path.join(rootPath, CONFIG_DOC_DIR, USER_SETTINGS_FILE);
16
- const projectSettingsPath = path.join(rootPath, CONFIG_DOC_DIR, PROJECT_SETTINGS_FILE);
17
17
 
18
18
  // ユーザ設定を読み込み
19
19
  let userSettings: UserSettings;
@@ -28,22 +28,14 @@ export async function GET() {
28
28
  };
29
29
  }
30
30
 
31
- // プロジェクト設定を読み込み
32
- let projectSettings: ProjectSettings;
33
- try {
34
- const content = await fs.readFile(projectSettingsPath, 'utf-8');
35
- projectSettings = JSON.parse(content);
36
- } catch {
37
- // デフォルトのプロジェクト設定
38
- projectSettings = {
39
- fileName: 'config-doc'
40
- };
41
- }
31
+ // プロジェクト設定から export.fileName を取得
32
+ const projectSettings = await fsService.loadProjectSettings();
33
+ const fileName = projectSettings?.export?.fileName || 'config-doc';
42
34
 
43
35
  // 統合された設定を返す
44
36
  const settings: ExportSettings = {
45
37
  ...userSettings,
46
- ...projectSettings
38
+ fileName
47
39
  };
48
40
 
49
41
  return NextResponse.json({
@@ -76,6 +68,7 @@ export async function POST(request: NextRequest) {
76
68
  }
77
69
 
78
70
  const rootPath = getRootPath();
71
+ const fsService = new FileSystemService(rootPath);
79
72
  const configDocDir = path.join(rootPath, CONFIG_DOC_DIR);
80
73
 
81
74
  // .config_doc ディレクトリを確保
@@ -94,17 +87,28 @@ export async function POST(request: NextRequest) {
94
87
  'utf-8'
95
88
  );
96
89
 
97
- // プロジェクト設定を保存(fileName
90
+ // プロジェクト設定の export.fileName を更新
98
91
  if (settings.fileName !== undefined) {
99
- const projectSettings: ProjectSettings = {
100
- fileName: settings.fileName
101
- };
102
- const projectSettingsPath = path.join(configDocDir, PROJECT_SETTINGS_FILE);
103
- await fs.writeFile(
104
- projectSettingsPath,
105
- JSON.stringify(projectSettings, null, 2),
106
- 'utf-8'
107
- );
92
+ const existingProjectSettings = await fsService.loadProjectSettings();
93
+
94
+ if (existingProjectSettings) {
95
+ // 既存の設定を更新
96
+ existingProjectSettings.export = {
97
+ ...existingProjectSettings.export,
98
+ fileName: settings.fileName
99
+ };
100
+ await fsService.saveProjectSettings(existingProjectSettings);
101
+ } else {
102
+ // 新規作成
103
+ const newProjectSettings: ProjectSettings = {
104
+ projectName: path.basename(rootPath),
105
+ configFiles: [],
106
+ export: {
107
+ fileName: settings.fileName
108
+ }
109
+ };
110
+ await fsService.saveProjectSettings(newProjectSettings);
111
+ }
108
112
  }
109
113
 
110
114
  return NextResponse.json({
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- import { ProjectConfigFiles, ConfigDocs, FileSystemItem } from '@/types';
3
+ import { ProjectConfigFiles, ConfigDocs, FileSystemItem, ProjectSettings } from '@/types';
4
4
 
5
5
  export class FileSystemService {
6
6
  private configDocDir = '.config_doc';
@@ -25,6 +25,37 @@ export class FileSystemService {
25
25
  await fs.mkdir(docsDir, { recursive: true });
26
26
  }
27
27
 
28
+ // 新しいプロジェクト設定の読み込み
29
+ async loadProjectSettings(): Promise<ProjectSettings | null> {
30
+ const settingsPath = path.join(
31
+ this.rootPath,
32
+ this.configDocDir,
33
+ 'project_settings.json'
34
+ );
35
+ try {
36
+ const content = await fs.readFile(settingsPath, 'utf-8');
37
+ return JSON.parse(content);
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ // 新しいプロジェクト設定の保存
44
+ async saveProjectSettings(settings: ProjectSettings): Promise<void> {
45
+ await this.ensureConfigDocDir();
46
+ const settingsPath = path.join(
47
+ this.rootPath,
48
+ this.configDocDir,
49
+ 'project_settings.json'
50
+ );
51
+ await fs.writeFile(
52
+ settingsPath,
53
+ JSON.stringify(settings, null, 2),
54
+ 'utf-8'
55
+ );
56
+ }
57
+
58
+ // 旧形式のconfig_files.jsonの読み込み(マイグレーション用)
28
59
  async loadConfigFiles(): Promise<ProjectConfigFiles | null> {
29
60
  const configFilesPath = path.join(
30
61
  this.rootPath,
@@ -1,4 +1,5 @@
1
1
  import { FileSystemService } from './fileSystem';
2
+ import { StorageService } from './storage';
2
3
  import { ProjectConfigFiles, ConfigDocs } from '@/types';
3
4
 
4
5
  interface ConfigWithDocs {
@@ -10,40 +11,53 @@ interface ConfigWithDocs {
10
11
 
11
12
  export class HtmlGenerator {
12
13
  private fsService: FileSystemService;
14
+ private storageService: StorageService;
13
15
 
14
16
  constructor(rootPath: string) {
15
17
  this.fsService = new FileSystemService(rootPath);
18
+ this.storageService = new StorageService(this.fsService);
16
19
  }
17
20
 
18
21
  async generateHtml(): Promise<string> {
19
- // メタデータを読み込む
20
- const metadata = await this.fsService.loadConfigFiles();
21
- if (!metadata || !metadata.configFiles || metadata.configFiles.length === 0) {
22
+ // プロジェクト設定を読み込む
23
+ const settings = await this.fsService.loadProjectSettings();
24
+ if (!settings || !settings.configFiles || settings.configFiles.length === 0) {
22
25
  return this.generateEmptyHtml();
23
26
  }
24
27
 
25
28
  // 各設定ファイルとそのドキュメントを読み込む
26
29
  const configs: ConfigWithDocs[] = [];
27
- for (const configFile of metadata.configFiles) {
30
+ for (const filePath of settings.configFiles) {
28
31
  try {
29
- const configData = await this.fsService.loadConfigFile(configFile.filePath);
30
- const docs = await this.fsService.loadConfigDocs(configFile.docsFileName);
32
+ const fileName = filePath.split(/[/\\]/).pop() || 'config.json';
33
+ const docsFileName = this.storageService.getDocsFileName(filePath);
34
+
35
+ const configData = await this.fsService.loadConfigFile(filePath);
36
+ const docs = await this.fsService.loadConfigDocs(docsFileName);
31
37
 
32
38
  configs.push({
33
- filePath: configFile.filePath,
34
- fileName: configFile.fileName,
39
+ filePath,
40
+ fileName,
35
41
  configData,
36
42
  docs: docs || {
37
- configFilePath: configFile.filePath,
43
+ configFilePath: filePath,
38
44
  lastModified: new Date().toISOString(),
39
45
  properties: {}
40
46
  }
41
47
  });
42
48
  } catch (error) {
43
- console.error(`Failed to load config: ${configFile.filePath}`, error);
49
+ console.error(`Failed to load config: ${filePath}`, error);
44
50
  }
45
51
  }
46
52
 
53
+ // 互換性のため ProjectConfigFiles 形式に変換
54
+ const metadata: ProjectConfigFiles = {
55
+ projectName: settings.projectName,
56
+ createdAt: '',
57
+ lastModified: new Date().toISOString(),
58
+ configFiles: []
59
+ };
60
+
47
61
  return this.generateFullHtml(metadata, configs);
48
62
  }
49
63
 
@@ -13,23 +13,24 @@ export class MarkdownGenerator {
13
13
  const fsService = new FileSystemService(this.rootPath);
14
14
  const storageService = new StorageService(fsService);
15
15
 
16
- // メタデータを読み込む
17
- const configFiles = await fsService.loadConfigFiles();
18
- if (!configFiles || configFiles.configFiles.length === 0) {
16
+ // プロジェクト設定を読み込む
17
+ const settings = await fsService.loadProjectSettings();
18
+ if (!settings || settings.configFiles.length === 0) {
19
19
  return '# 設定ファイルドキュメント\n\nドキュメント化された設定ファイルがありません。\n';
20
20
  }
21
21
 
22
22
  let markdown = '# 設定ファイルドキュメント\n\n';
23
- markdown += `プロジェクト: **${configFiles.projectName}**\n\n`;
24
- markdown += `最終更新: ${new Date(configFiles.lastModified).toLocaleString('ja-JP')}\n\n`;
23
+ markdown += `プロジェクト: **${settings.projectName}**\n\n`;
24
+ markdown += `最終更新: ${new Date().toLocaleString('ja-JP')}\n\n`;
25
25
  markdown += '---\n\n';
26
26
 
27
27
  // 各設定ファイルのドキュメントを生成
28
- for (const configFile of configFiles.configFiles) {
29
- const docs = await storageService.loadAllDocs(configFile.filePath);
28
+ for (const filePath of settings.configFiles) {
29
+ const fileName = filePath.split(/[/\\]/).pop() || 'config.json';
30
+ const docs = await storageService.loadAllDocs(filePath);
30
31
 
31
- markdown += `## ${configFile.fileName}\n\n`;
32
- markdown += `**ファイルパス:** \`${configFile.filePath}\`\n\n`;
32
+ markdown += `## ${fileName}\n\n`;
33
+ markdown += `**ファイルパス:** \`${filePath}\`\n\n`;
33
34
 
34
35
  const propertyEntries = Object.entries(docs.properties);
35
36
  if (propertyEntries.length === 0) {
@@ -13,24 +13,25 @@ export class MarkdownTableGenerator {
13
13
  const fsService = new FileSystemService(this.rootPath);
14
14
  const storageService = new StorageService(fsService);
15
15
 
16
- // メタデータを読み込む
17
- const configFiles = await fsService.loadConfigFiles();
18
- if (!configFiles || configFiles.configFiles.length === 0) {
16
+ // プロジェクト設定を読み込む
17
+ const settings = await fsService.loadProjectSettings();
18
+ if (!settings || settings.configFiles.length === 0) {
19
19
  return '# 設定ファイルドキュメント\n\nドキュメント化された設定ファイルがありません。\n';
20
20
  }
21
21
 
22
22
  let markdown = '# 設定ファイルドキュメント\n\n';
23
- markdown += `プロジェクト: **${configFiles.projectName}**\n\n`;
24
- markdown += `最終更新: ${new Date(configFiles.lastModified).toLocaleString('ja-JP')}\n\n`;
23
+ markdown += `プロジェクト: **${settings.projectName}**\n\n`;
24
+ markdown += `最終更新: ${new Date().toLocaleString('ja-JP')}\n\n`;
25
25
  markdown += '---\n\n';
26
26
 
27
27
  // 各設定ファイルのドキュメントを生成
28
- for (const configFile of configFiles.configFiles) {
29
- const docs = await storageService.loadAllDocs(configFile.filePath);
30
- const configData = await fsService.loadConfigFile(configFile.filePath);
28
+ for (const filePath of settings.configFiles) {
29
+ const fileName = filePath.split(/[/\\]/).pop() || 'config.json';
30
+ const docs = await storageService.loadAllDocs(filePath);
31
+ const configData = await fsService.loadConfigFile(filePath);
31
32
 
32
- markdown += `## ${configFile.fileName}\n\n`;
33
- markdown += `**ファイルパス:** \`${configFile.filePath}\`\n\n`;
33
+ markdown += `## ${fileName}\n\n`;
34
+ markdown += `**ファイルパス:** \`${filePath}\`\n\n`;
34
35
 
35
36
  const propertyEntries = Object.entries(docs.properties);
36
37
  if (propertyEntries.length === 0) {
@@ -1,5 +1,6 @@
1
1
  import { FileSystemService } from './fileSystem';
2
2
  import { ConfigDocs, PropertyDoc } from '@/types';
3
+ import path from 'path';
3
4
 
4
5
  export class StorageService {
5
6
  constructor(private fs: FileSystemService) {}
@@ -11,10 +12,13 @@ export class StorageService {
11
12
  ): Promise<void> {
12
13
  const docsFileName = this.getDocsFileName(configFilePath);
13
14
 
15
+ // 絶対パスを相対パスに変換
16
+ const relativeConfigPath = this.toRelativePath(configFilePath);
17
+
14
18
  let docs = await this.fs.loadConfigDocs(docsFileName);
15
19
  if (!docs) {
16
20
  docs = {
17
- configFilePath,
21
+ configFilePath: relativeConfigPath,
18
22
  lastModified: new Date().toISOString(),
19
23
  properties: {}
20
24
  };
@@ -22,6 +26,7 @@ export class StorageService {
22
26
 
23
27
  docs.properties[propertyPath] = propertyDoc;
24
28
  docs.lastModified = new Date().toISOString();
29
+ docs.configFilePath = relativeConfigPath; // 常に相対パスを保存
25
30
 
26
31
  await this.fs.saveConfigDocs(docsFileName, docs);
27
32
  }
@@ -30,28 +35,45 @@ export class StorageService {
30
35
  const docsFileName = this.getDocsFileName(configFilePath);
31
36
  const docs = await this.fs.loadConfigDocs(docsFileName);
32
37
 
38
+ // 絶対パスを相対パスに変換
39
+ const relativeConfigPath = this.toRelativePath(configFilePath);
40
+
33
41
  return docs || {
34
- configFilePath,
42
+ configFilePath: relativeConfigPath,
35
43
  lastModified: new Date().toISOString(),
36
44
  properties: {}
37
45
  };
38
46
  }
39
47
 
40
- private getDocsFileName(configFilePath: string): string {
41
- // 絶対パスの場合は、パス全体からハッシュを生成して一意にする
42
- const isAbsolute = /^[a-zA-Z]:[/\\]/.test(configFilePath) || configFilePath.startsWith('/');
43
-
44
- if (isAbsolute) {
45
- // 絶対パスの場合:パスを正規化してBase64エンコード(ファイル名として安全な形式)
46
- const normalized = configFilePath.replace(/[/\\]/g, '_').replace(/:/g, '');
47
- const fileName = configFilePath.split(/[/\\]/).pop() || 'config.json';
48
- const baseName = fileName.replace('.json', '');
49
- // ファイル名の前にパスのハッシュを追加して一意性を確保
50
- return `${normalized.substring(0, 50)}_${baseName}.docs.json`;
51
- } else {
52
- // 相対パスの場合:従来通り
53
- const fileName = configFilePath.split(/[/\\]/).pop() || 'config.json';
48
+ // 公開メソッド: docsファイル名を取得
49
+ public getDocsFileName(configFilePath: string): string {
50
+ // 相対パスに変換してから処理
51
+ const relativePath = this.toRelativePath(configFilePath);
52
+
53
+ // パスの深さが1以下(ルート直下のファイル)の場合は、シンプルなファイル名
54
+ const pathParts = relativePath.split(/[/\\]/).filter(p => p && p !== '.');
55
+
56
+ if (pathParts.length <= 1) {
57
+ // ルート直下のファイル: components.json → components.docs.json
58
+ const fileName = pathParts[0] || 'config.json';
54
59
  return fileName.replace('.json', '.docs.json');
60
+ } else {
61
+ // サブディレクトリのファイル: 相対パスを含めて一意にする
62
+ // 例: ../SimMoney/components.json → __SimMoney_components.docs.json
63
+ // 例: sample/appsettings.json → sample_appsettings.docs.json
64
+ const normalized = relativePath
65
+ .replace(/\.\./g, '__') // .. を __ に変換
66
+ .replace(/[/\\]/g, '_') // / と \ を _ に変換
67
+ .replace(/:/g, ''); // : を削除
68
+ return normalized.replace('.json', '.docs.json');
69
+ }
70
+ }
71
+
72
+ private toRelativePath(filePath: string): string {
73
+ // 絶対パスの場合は相対パスに変換
74
+ if (path.isAbsolute(filePath)) {
75
+ return path.relative(this.fs['rootPath'], filePath);
55
76
  }
77
+ return filePath;
56
78
  }
57
79
  }
@@ -45,12 +45,16 @@ export interface FileSystemItem {
45
45
 
46
46
  export type ExportFormat = 'html' | 'markdown' | 'markdown-table';
47
47
 
48
- // チーム共有設定(settings.json
48
+ // プロジェクト設定(project_settings.json)- チーム共有
49
49
  export interface ProjectSettings {
50
- fileName?: string; // 出力ファイル名(拡張子なし、デフォルト: config-doc)
50
+ projectName: string;
51
+ configFiles: string[]; // 設定ファイルの相対パス配列
52
+ export?: {
53
+ fileName?: string; // 出力ファイル名(拡張子なし、デフォルト: config-doc)
54
+ };
51
55
  }
52
56
 
53
- // ユーザ個別設定(.user.local.json)
57
+ // ユーザ個別設定(.user_settings.json)
54
58
  export interface UserSettings {
55
59
  format: ExportFormat; // 出力形式
56
60
  autoExport: boolean; // 保存時に自動エクスポート
@@ -58,4 +62,6 @@ export interface UserSettings {
58
62
  }
59
63
 
60
64
  // 統合された設定(レスポンス用)
61
- export interface ExportSettings extends UserSettings, ProjectSettings {}
65
+ export interface ExportSettings extends UserSettings {
66
+ fileName?: string; // ProjectSettings.export.fileName から取得
67
+ }