vimd 0.1.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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/dist/cli/commands/build.d.ts +7 -0
  4. package/dist/cli/commands/build.d.ts.map +1 -0
  5. package/dist/cli/commands/build.js +54 -0
  6. package/dist/cli/commands/config.d.ts +6 -0
  7. package/dist/cli/commands/config.d.ts.map +1 -0
  8. package/dist/cli/commands/config.js +117 -0
  9. package/dist/cli/commands/dev.d.ts +8 -0
  10. package/dist/cli/commands/dev.d.ts.map +1 -0
  11. package/dist/cli/commands/dev.js +95 -0
  12. package/dist/cli/commands/theme.d.ts +2 -0
  13. package/dist/cli/commands/theme.d.ts.map +1 -0
  14. package/dist/cli/commands/theme.js +46 -0
  15. package/dist/cli/index.d.ts +3 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +38 -0
  18. package/dist/cli/setup.d.ts +2 -0
  19. package/dist/cli/setup.d.ts.map +1 -0
  20. package/dist/cli/setup.js +50 -0
  21. package/dist/config/defaults.d.ts +3 -0
  22. package/dist/config/defaults.d.ts.map +1 -0
  23. package/dist/config/defaults.js +19 -0
  24. package/dist/config/loader.d.ts +9 -0
  25. package/dist/config/loader.d.ts.map +1 -0
  26. package/dist/config/loader.js +78 -0
  27. package/dist/config/types.d.ts +47 -0
  28. package/dist/config/types.d.ts.map +1 -0
  29. package/dist/config/types.js +4 -0
  30. package/dist/config/validator.d.ts +11 -0
  31. package/dist/config/validator.d.ts.map +1 -0
  32. package/dist/config/validator.js +38 -0
  33. package/dist/core/converter.d.ts +10 -0
  34. package/dist/core/converter.d.ts.map +1 -0
  35. package/dist/core/converter.js +81 -0
  36. package/dist/core/pandoc-detector.d.ts +8 -0
  37. package/dist/core/pandoc-detector.d.ts.map +1 -0
  38. package/dist/core/pandoc-detector.js +70 -0
  39. package/dist/core/server.d.ts +11 -0
  40. package/dist/core/server.d.ts.map +1 -0
  41. package/dist/core/server.js +57 -0
  42. package/dist/core/watcher.d.ts +16 -0
  43. package/dist/core/watcher.d.ts.map +1 -0
  44. package/dist/core/watcher.js +44 -0
  45. package/dist/index.d.ts +14 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +18 -0
  48. package/dist/templates/.gitkeep +0 -0
  49. package/dist/templates/default.html +22 -0
  50. package/dist/templates/standalone.html +31 -0
  51. package/dist/themes/index.d.ts +7 -0
  52. package/dist/themes/index.d.ts.map +1 -0
  53. package/dist/themes/index.js +29 -0
  54. package/dist/themes/registry.d.ts +4 -0
  55. package/dist/themes/registry.d.ts.map +1 -0
  56. package/dist/themes/registry.js +40 -0
  57. package/dist/themes/styles/.gitkeep +0 -0
  58. package/dist/themes/styles/academic.css +68 -0
  59. package/dist/themes/styles/dark.css +54 -0
  60. package/dist/themes/styles/github.css +1 -0
  61. package/dist/themes/styles/minimal.css +35 -0
  62. package/dist/themes/styles/technical.css +84 -0
  63. package/dist/types/index.d.ts +2 -0
  64. package/dist/types/index.d.ts.map +1 -0
  65. package/dist/types/index.js +2 -0
  66. package/dist/utils/logger.d.ts +7 -0
  67. package/dist/utils/logger.d.ts.map +1 -0
  68. package/dist/utils/logger.js +16 -0
  69. package/dist/utils/os-detector.d.ts +8 -0
  70. package/dist/utils/os-detector.d.ts.map +1 -0
  71. package/dist/utils/os-detector.js +34 -0
  72. package/dist/utils/path-resolver.d.ts +7 -0
  73. package/dist/utils/path-resolver.d.ts.map +1 -0
  74. package/dist/utils/path-resolver.js +26 -0
  75. package/dist/utils/process-manager.d.ts +12 -0
  76. package/dist/utils/process-manager.d.ts.map +1 -0
  77. package/dist/utils/process-manager.js +35 -0
  78. package/package.json +75 -0
  79. package/templates/.gitkeep +0 -0
  80. package/templates/default.html +22 -0
  81. package/templates/standalone.html +31 -0
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { devCommand } from './commands/dev.js';
4
+ import { buildCommand } from './commands/build.js';
5
+ import { themeCommand } from './commands/theme.js';
6
+ import { configCommand } from './commands/config.js';
7
+ const program = new Command();
8
+ program
9
+ .name('vimd')
10
+ .description('Real-time Markdown preview tool (view markdown)')
11
+ .version('0.1.0');
12
+ // vimd dev <file>
13
+ program
14
+ .command('dev <file>')
15
+ .description('Start live preview server')
16
+ .option('-p, --port <port>', 'Port number', '8080')
17
+ .option('-t, --theme <theme>', 'Theme name')
18
+ .option('--no-open', 'Do not open browser automatically')
19
+ .action(devCommand);
20
+ // vimd build <file>
21
+ program
22
+ .command('build <file>')
23
+ .description('Build static HTML file')
24
+ .option('-o, --output <path>', 'Output file path')
25
+ .option('-t, --theme <theme>', 'Theme name')
26
+ .action(buildCommand);
27
+ // vimd theme
28
+ program
29
+ .command('theme')
30
+ .description('Change theme interactively')
31
+ .action(themeCommand);
32
+ // vimd config
33
+ program
34
+ .command('config')
35
+ .description('Edit configuration interactively')
36
+ .option('-l, --list', 'List current configuration')
37
+ .action(configCommand);
38
+ program.parse(process.argv);
@@ -0,0 +1,2 @@
1
+ export declare function setupCommand(): Promise<void>;
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/cli/setup.ts"],"names":[],"mappings":"AASA,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CA+ClD"}
@@ -0,0 +1,50 @@
1
+ // src/cli/setup.ts
2
+ import inquirer from 'inquirer';
3
+ import { ConfigLoader } from '../config/loader.js';
4
+ import { DEFAULT_CONFIG } from '../config/defaults.js';
5
+ import { ThemeManager } from '../themes/index.js';
6
+ import { Logger } from '../utils/logger.js';
7
+ import { PathResolver } from '../utils/path-resolver.js';
8
+ import fs from 'fs-extra';
9
+ export async function setupCommand() {
10
+ console.log('\nWelcome to vimd!\n');
11
+ const themes = ThemeManager.list();
12
+ const answers = await inquirer.prompt([
13
+ {
14
+ type: 'list',
15
+ name: 'theme',
16
+ message: 'Select a theme:',
17
+ choices: themes.map((t) => ({
18
+ name: `${t.displayName} - ${t.description}`,
19
+ value: t.name,
20
+ })),
21
+ default: 'github',
22
+ },
23
+ ]);
24
+ const config = {
25
+ ...DEFAULT_CONFIG,
26
+ theme: answers.theme,
27
+ };
28
+ try {
29
+ const configPath = PathResolver.getConfigPath();
30
+ const configDir = PathResolver.getConfigDir();
31
+ // Create configuration directory
32
+ Logger.info(`Creating configuration directory: ${configDir}`);
33
+ await fs.ensureDir(configDir);
34
+ // Save configuration
35
+ Logger.info(`Saving configuration: ${configPath}`);
36
+ await ConfigLoader.save(config, configPath);
37
+ Logger.success('\nSetup complete!\n');
38
+ console.log('Get started:');
39
+ console.log(' vimd dev README.md - Start preview');
40
+ console.log(' vimd theme - Change theme');
41
+ console.log(' vimd config - Advanced settings\n');
42
+ }
43
+ catch (error) {
44
+ Logger.error('Setup failed');
45
+ if (error instanceof Error) {
46
+ Logger.error(error.message);
47
+ }
48
+ process.exit(1);
49
+ }
50
+ }
@@ -0,0 +1,3 @@
1
+ import { VimdConfig } from './types.js';
2
+ export declare const DEFAULT_CONFIG: VimdConfig;
3
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,eAAO,MAAM,cAAc,EAAE,UAkB5B,CAAC"}
@@ -0,0 +1,19 @@
1
+ export const DEFAULT_CONFIG = {
2
+ theme: 'github',
3
+ port: 8080,
4
+ host: 'localhost',
5
+ open: true,
6
+ pandoc: {
7
+ standalone: true,
8
+ toc: false,
9
+ tocDepth: 3,
10
+ },
11
+ watch: {
12
+ ignored: ['node_modules/**', '.git/**', 'dist/**'],
13
+ debounce: 500,
14
+ },
15
+ build: {
16
+ inlineCSS: false,
17
+ standalone: true,
18
+ },
19
+ };
@@ -0,0 +1,9 @@
1
+ import { VimdConfig } from './types.js';
2
+ export declare class ConfigLoader {
3
+ static merge(partial: Partial<VimdConfig>): VimdConfig;
4
+ static save(config: VimdConfig, configPath?: string): Promise<void>;
5
+ static loadGlobal(configPath?: string): Promise<VimdConfig>;
6
+ private static generateConfigFile;
7
+ private static parseConfigFile;
8
+ }
9
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMxC,qBAAa,YAAY;IACvB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU;WAyBzC,IAAI,CACf,MAAM,EAAE,UAAU,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;WAYH,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAqBjE,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAOjC,OAAO,CAAC,MAAM,CAAC,eAAe;CAa/B"}
@@ -0,0 +1,78 @@
1
+ import { DEFAULT_CONFIG } from './defaults.js';
2
+ import { PathResolver } from '../utils/path-resolver.js';
3
+ import fs from 'fs-extra';
4
+ import * as path from 'path';
5
+ export class ConfigLoader {
6
+ static merge(partial) {
7
+ return {
8
+ ...DEFAULT_CONFIG,
9
+ ...partial,
10
+ pandoc: partial.pandoc
11
+ ? {
12
+ ...DEFAULT_CONFIG.pandoc,
13
+ ...partial.pandoc,
14
+ }
15
+ : DEFAULT_CONFIG.pandoc,
16
+ watch: partial.watch
17
+ ? {
18
+ ...DEFAULT_CONFIG.watch,
19
+ ...partial.watch,
20
+ }
21
+ : DEFAULT_CONFIG.watch,
22
+ build: partial.build
23
+ ? {
24
+ ...DEFAULT_CONFIG.build,
25
+ ...partial.build,
26
+ }
27
+ : DEFAULT_CONFIG.build,
28
+ };
29
+ }
30
+ static async save(config, configPath) {
31
+ const targetPath = configPath || PathResolver.getConfigPath();
32
+ const configDir = path.dirname(targetPath);
33
+ // ディレクトリ作成
34
+ await fs.ensureDir(configDir);
35
+ // TypeScript設定ファイルとして出力
36
+ const content = this.generateConfigFile(config);
37
+ await fs.writeFile(targetPath, content, 'utf-8');
38
+ }
39
+ static async loadGlobal(configPath) {
40
+ const targetPath = configPath || PathResolver.getConfigPath();
41
+ // ファイルが存在しない場合はデフォルト設定を返す
42
+ if (!(await fs.pathExists(targetPath))) {
43
+ return DEFAULT_CONFIG;
44
+ }
45
+ try {
46
+ // TypeScript設定ファイルを動的にインポート
47
+ // 注: 実際の実装では、tsx等でTypeScriptファイルを実行する必要がある
48
+ // 今回は簡易的にJSONとしてパース
49
+ const content = await fs.readFile(targetPath, 'utf-8');
50
+ const config = this.parseConfigFile(content);
51
+ return this.merge(config);
52
+ }
53
+ catch (error) {
54
+ console.error('Failed to load config file:', error);
55
+ return DEFAULT_CONFIG;
56
+ }
57
+ }
58
+ static generateConfigFile(config) {
59
+ return `import { defineConfig } from 'vimd';
60
+
61
+ export default defineConfig(${JSON.stringify(config, null, 2)});
62
+ `;
63
+ }
64
+ static parseConfigFile(content) {
65
+ // 簡易的なパース (実際はtsx等でTypeScriptを実行)
66
+ // JSON部分を抽出
67
+ const match = content.match(/defineConfig\(([\s\S]*)\);/);
68
+ if (match) {
69
+ try {
70
+ return JSON.parse(match[1]);
71
+ }
72
+ catch {
73
+ return {};
74
+ }
75
+ }
76
+ return {};
77
+ }
78
+ }
@@ -0,0 +1,47 @@
1
+ export interface VimdConfig {
2
+ theme: 'github' | 'minimal' | 'dark' | 'academic' | 'technical';
3
+ port: number;
4
+ host: string;
5
+ open: boolean;
6
+ css?: string;
7
+ template?: string;
8
+ pandoc: PandocConfig;
9
+ watch: WatchConfig;
10
+ build?: BuildConfig;
11
+ }
12
+ export interface PandocConfig {
13
+ standalone: boolean;
14
+ toc: boolean;
15
+ tocDepth?: number;
16
+ highlightStyle?: string;
17
+ metadata?: Record<string, string>;
18
+ }
19
+ export interface WatchConfig {
20
+ ignored: string[];
21
+ debounce: number;
22
+ }
23
+ export interface BuildConfig {
24
+ output?: string;
25
+ inlineCSS: boolean;
26
+ standalone: boolean;
27
+ }
28
+ export interface ThemeInfo {
29
+ name: string;
30
+ displayName: string;
31
+ description: string;
32
+ cssPath: string;
33
+ }
34
+ export interface ServerConfig {
35
+ port: number;
36
+ host: string;
37
+ open: boolean;
38
+ root: string;
39
+ }
40
+ export interface ConverterConfig {
41
+ theme: string;
42
+ pandocOptions: PandocConfig;
43
+ customCSS?: string;
44
+ template?: string;
45
+ }
46
+ export declare function defineConfig(config: Partial<VimdConfig>): VimdConfig;
47
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAEpE"}
@@ -0,0 +1,4 @@
1
+ // src/config/types.ts
2
+ export function defineConfig(config) {
3
+ return config;
4
+ }
@@ -0,0 +1,11 @@
1
+ import { VimdConfig } from './types.js';
2
+ export interface ValidationResult {
3
+ valid: boolean;
4
+ errors: string[];
5
+ }
6
+ export declare class ConfigValidator {
7
+ static validatePort(port: number): boolean;
8
+ static validateTheme(theme: string): boolean;
9
+ static validate(config: VimdConfig): ValidationResult;
10
+ }
11
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/config/validator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAID,qBAAa,eAAe;IAC1B,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1C,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAI5C,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB;CAuCtD"}
@@ -0,0 +1,38 @@
1
+ const VALID_THEMES = ['github', 'minimal', 'dark', 'academic', 'technical'];
2
+ export class ConfigValidator {
3
+ static validatePort(port) {
4
+ return Number.isInteger(port) && port > 0 && port <= 65535;
5
+ }
6
+ static validateTheme(theme) {
7
+ return VALID_THEMES.includes(theme);
8
+ }
9
+ static validate(config) {
10
+ const errors = [];
11
+ // Port validation
12
+ if (!this.validatePort(config.port)) {
13
+ errors.push(`Invalid port number: ${config.port}`);
14
+ }
15
+ // Theme validation
16
+ if (!this.validateTheme(config.theme)) {
17
+ errors.push(`Invalid theme: ${config.theme}`);
18
+ }
19
+ // Host validation
20
+ if (!config.host || config.host.trim() === '') {
21
+ errors.push('Host cannot be empty');
22
+ }
23
+ // Pandoc validation
24
+ if (config.pandoc.tocDepth !== undefined) {
25
+ if (config.pandoc.tocDepth < 1 || config.pandoc.tocDepth > 6) {
26
+ errors.push(`Invalid tocDepth: ${config.pandoc.tocDepth} (must be 1-6)`);
27
+ }
28
+ }
29
+ // Watch debounce validation
30
+ if (config.watch.debounce < 0) {
31
+ errors.push(`Invalid debounce: ${config.watch.debounce} (must be >= 0)`);
32
+ }
33
+ return {
34
+ valid: errors.length === 0,
35
+ errors,
36
+ };
37
+ }
38
+ }
@@ -0,0 +1,10 @@
1
+ import { ConverterConfig } from '../config/types.js';
2
+ export declare class MarkdownConverter {
3
+ private config;
4
+ constructor(config: ConverterConfig);
5
+ convert(markdownPath: string): Promise<string>;
6
+ convertWithTemplate(markdownPath: string): Promise<string>;
7
+ writeHTML(html: string, outputPath: string): Promise<void>;
8
+ private buildPandocArgs;
9
+ }
10
+ //# sourceMappingURL=converter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../src/core/converter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AASrD,qBAAa,iBAAiB;IAChB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,eAAe;IAErC,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB9C,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8B1D,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhE,OAAO,CAAC,eAAe;CA+BxB"}
@@ -0,0 +1,81 @@
1
+ // src/core/converter.ts
2
+ import { execSync } from 'child_process';
3
+ import { ThemeManager } from '../themes/index.js';
4
+ import fs from 'fs-extra';
5
+ import * as path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ export class MarkdownConverter {
10
+ constructor(config) {
11
+ this.config = config;
12
+ }
13
+ async convert(markdownPath) {
14
+ const pandocArgs = this.buildPandocArgs();
15
+ const command = `pandoc ${pandocArgs.join(' ')} "${markdownPath}"`;
16
+ try {
17
+ const html = execSync(command, {
18
+ encoding: 'utf-8',
19
+ maxBuffer: 10 * 1024 * 1024, // 10MB
20
+ });
21
+ return html;
22
+ }
23
+ catch (error) {
24
+ const errorMessage = error instanceof Error ? error.message : String(error);
25
+ throw new Error(`Failed to convert markdown: ${errorMessage}`);
26
+ }
27
+ }
28
+ async convertWithTemplate(markdownPath) {
29
+ const contentHtml = await this.convert(markdownPath);
30
+ const themeCSS = await ThemeManager.getCSS(this.config.theme);
31
+ let customCSS = '';
32
+ if (this.config.customCSS) {
33
+ customCSS = await ThemeManager.loadCustomCSS(this.config.customCSS);
34
+ }
35
+ const templatePath = this.config.template
36
+ ? this.config.template
37
+ : path.join(__dirname, '../../templates/default.html');
38
+ const template = await fs.readFile(templatePath, 'utf-8');
39
+ // Simple template replacement
40
+ let html = template
41
+ .replace('{{title}}', path.basename(markdownPath, '.md'))
42
+ .replace('{{theme_css}}', themeCSS)
43
+ .replace('{{content}}', contentHtml);
44
+ if (customCSS) {
45
+ html = html.replace('{{custom_css}}', customCSS);
46
+ }
47
+ else {
48
+ html = html.replace(/\{\{#if custom_css\}\}[\s\S]*?\{\{\/if\}\}/g, '');
49
+ }
50
+ return html;
51
+ }
52
+ async writeHTML(html, outputPath) {
53
+ await fs.ensureDir(path.dirname(outputPath));
54
+ await fs.writeFile(outputPath, html, 'utf-8');
55
+ }
56
+ buildPandocArgs() {
57
+ const args = [];
58
+ // Basic options
59
+ args.push('--from=markdown');
60
+ args.push('--to=html');
61
+ if (this.config.pandocOptions.standalone) {
62
+ args.push('--standalone');
63
+ }
64
+ if (this.config.pandocOptions.toc) {
65
+ args.push('--toc');
66
+ if (this.config.pandocOptions.tocDepth) {
67
+ args.push(`--toc-depth=${this.config.pandocOptions.tocDepth}`);
68
+ }
69
+ }
70
+ if (this.config.pandocOptions.highlightStyle) {
71
+ args.push(`--highlight-style=${this.config.pandocOptions.highlightStyle}`);
72
+ }
73
+ // Metadata
74
+ if (this.config.pandocOptions.metadata) {
75
+ Object.entries(this.config.pandocOptions.metadata).forEach(([key, value]) => {
76
+ args.push(`--metadata=${key}:"${value}"`);
77
+ });
78
+ }
79
+ return args;
80
+ }
81
+ }
@@ -0,0 +1,8 @@
1
+ import { OSType } from '../utils/os-detector.js';
2
+ export declare class PandocDetector {
3
+ static check(): boolean;
4
+ static detectOS(): OSType;
5
+ static showInstallGuide(os: OSType): void;
6
+ static ensureInstalled(): void;
7
+ }
8
+ //# sourceMappingURL=pandoc-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pandoc-detector.d.ts","sourceRoot":"","sources":["../../src/core/pandoc-detector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD,qBAAa,cAAc;IACzB,MAAM,CAAC,KAAK,IAAI,OAAO;IASvB,MAAM,CAAC,QAAQ,IAAI,MAAM;IAczB,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IA4CzC,MAAM,CAAC,eAAe,IAAI,IAAI;CAQ/B"}
@@ -0,0 +1,70 @@
1
+ // src/core/pandoc-detector.ts
2
+ import { execSync } from 'child_process';
3
+ export class PandocDetector {
4
+ static check() {
5
+ try {
6
+ execSync('pandoc --version', { stdio: 'pipe' });
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ static detectOS() {
14
+ switch (process.platform) {
15
+ case 'darwin':
16
+ return 'macos';
17
+ case 'win32':
18
+ return 'windows';
19
+ case 'linux':
20
+ // Simplified: default to debian-based
21
+ return 'linux-debian';
22
+ default:
23
+ return 'linux-debian';
24
+ }
25
+ }
26
+ static showInstallGuide(os) {
27
+ console.error('⚠️ pandoc not found');
28
+ console.error('');
29
+ console.error('vimd requires pandoc to convert Markdown to HTML.');
30
+ console.error('Please install pandoc manually:');
31
+ console.error('');
32
+ switch (os) {
33
+ case 'macos':
34
+ console.error(' macOS (Homebrew):');
35
+ console.error(' brew install pandoc');
36
+ console.error('');
37
+ console.error(' macOS (Official installer):');
38
+ console.error(' https://github.com/jgm/pandoc/releases');
39
+ break;
40
+ case 'linux-debian':
41
+ console.error(' Debian/Ubuntu:');
42
+ console.error(' sudo apt-get update');
43
+ console.error(' sudo apt-get install pandoc');
44
+ break;
45
+ case 'linux-redhat':
46
+ console.error(' RedHat/CentOS/Fedora:');
47
+ console.error(' sudo yum install pandoc');
48
+ break;
49
+ case 'windows':
50
+ console.error(' Windows (Chocolatey):');
51
+ console.error(' choco install pandoc');
52
+ console.error('');
53
+ console.error(' Windows (Official installer):');
54
+ console.error(' https://github.com/jgm/pandoc/releases');
55
+ break;
56
+ }
57
+ console.error('');
58
+ console.error('For more installation options:');
59
+ console.error(' https://pandoc.org/installing.html');
60
+ console.error('');
61
+ process.exit(1);
62
+ }
63
+ static ensureInstalled() {
64
+ if (this.check()) {
65
+ return;
66
+ }
67
+ const os = this.detectOS();
68
+ this.showInstallGuide(os);
69
+ }
70
+ }
@@ -0,0 +1,11 @@
1
+ import { ServerConfig } from '../config/types.js';
2
+ export declare class LiveServer {
3
+ private config;
4
+ private running;
5
+ constructor(config: ServerConfig);
6
+ start(htmlPath: string): Promise<void>;
7
+ stop(): Promise<void>;
8
+ openBrowser(url: string): Promise<void>;
9
+ getURL(): string;
10
+ }
11
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKlD,qBAAa,UAAU;IAGT,OAAO,CAAC,MAAM;IAF1B,OAAO,CAAC,OAAO,CAAS;gBAEJ,MAAM,EAAE,YAAY;IAElC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAUrB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7C,MAAM,IAAI,MAAM;CAGjB"}
@@ -0,0 +1,57 @@
1
+ // src/core/server.ts
2
+ import * as liveServer from 'live-server';
3
+ import { Logger } from '../utils/logger.js';
4
+ import * as path from 'path';
5
+ import open from 'open';
6
+ export class LiveServer {
7
+ constructor(config) {
8
+ this.config = config;
9
+ this.running = false;
10
+ }
11
+ async start(htmlPath) {
12
+ const root = path.dirname(htmlPath);
13
+ const file = path.basename(htmlPath);
14
+ const params = {
15
+ port: this.config.port,
16
+ host: this.config.host,
17
+ root: root,
18
+ file: file,
19
+ open: false, // manually open
20
+ wait: 200,
21
+ logLevel: 0, // silent
22
+ };
23
+ try {
24
+ liveServer.start(params);
25
+ this.running = true;
26
+ const url = `http://${this.config.host}:${this.config.port}`;
27
+ Logger.success(`Server started at ${url}`);
28
+ if (this.config.open) {
29
+ await this.openBrowser(url);
30
+ }
31
+ }
32
+ catch (error) {
33
+ const errorMessage = error instanceof Error ? error.message : String(error);
34
+ throw new Error(`Failed to start server: ${errorMessage}`);
35
+ }
36
+ }
37
+ async stop() {
38
+ if (!this.running) {
39
+ return;
40
+ }
41
+ liveServer.shutdown();
42
+ this.running = false;
43
+ Logger.info('Server stopped');
44
+ }
45
+ async openBrowser(url) {
46
+ try {
47
+ await open(url);
48
+ Logger.info('Browser opened');
49
+ }
50
+ catch (error) {
51
+ Logger.warn('Failed to open browser automatically');
52
+ }
53
+ }
54
+ getURL() {
55
+ return `http://${this.config.host}:${this.config.port}`;
56
+ }
57
+ }
@@ -0,0 +1,16 @@
1
+ import { WatchConfig } from '../config/types.js';
2
+ type ChangeCallback = (path: string) => void;
3
+ export declare class FileWatcher {
4
+ private filePath;
5
+ private config;
6
+ private watcher;
7
+ private callbacks;
8
+ private debounceTimer;
9
+ constructor(filePath: string, config: WatchConfig);
10
+ onChange(callback: ChangeCallback): void;
11
+ start(): void;
12
+ stop(): Promise<void>;
13
+ private handleChange;
14
+ }
15
+ export {};
16
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/core/watcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE7C,qBAAa,WAAW;IAMpB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,aAAa,CAA+B;gBAG1C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW;IAG7B,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAIxC,KAAK,IAAI,IAAI;IAYP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,OAAO,CAAC,YAAY;CAWrB"}
@@ -0,0 +1,44 @@
1
+ // src/core/watcher.ts
2
+ import chokidar from 'chokidar';
3
+ export class FileWatcher {
4
+ constructor(filePath, config) {
5
+ this.filePath = filePath;
6
+ this.config = config;
7
+ this.watcher = null;
8
+ this.callbacks = [];
9
+ this.debounceTimer = null;
10
+ }
11
+ onChange(callback) {
12
+ this.callbacks.push(callback);
13
+ }
14
+ start() {
15
+ this.watcher = chokidar.watch(this.filePath, {
16
+ ignored: this.config.ignored,
17
+ persistent: true,
18
+ ignoreInitial: true,
19
+ });
20
+ this.watcher.on('change', (path) => {
21
+ this.handleChange(path);
22
+ });
23
+ }
24
+ async stop() {
25
+ if (this.debounceTimer) {
26
+ clearTimeout(this.debounceTimer);
27
+ this.debounceTimer = null;
28
+ }
29
+ if (this.watcher) {
30
+ await this.watcher.close();
31
+ this.watcher = null;
32
+ }
33
+ }
34
+ handleChange(path) {
35
+ // デバウンス処理
36
+ if (this.debounceTimer) {
37
+ clearTimeout(this.debounceTimer);
38
+ }
39
+ this.debounceTimer = setTimeout(() => {
40
+ this.callbacks.forEach((callback) => callback(path));
41
+ this.debounceTimer = null;
42
+ }, this.config.debounce);
43
+ }
44
+ }