mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			copilot/im
			...
			feat/push-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					53f31f9c78 | ||
| 
						 | 
					22c5651eeb | ||
| 
						 | 
					68a10a9813 | ||
| 
						 | 
					b204964e57 | ||
| 
						 | 
					b2c869d7ab | ||
| 
						 | 
					307e17f9c8 | 
							
								
								
									
										453
									
								
								.github/scripts/sync-docs-to-wiki-with-wiki-syntax.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										453
									
								
								.github/scripts/sync-docs-to-wiki-with-wiki-syntax.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,453 @@
 | 
			
		||||
#!/usr/bin/env tsx
 | 
			
		||||
 | 
			
		||||
import * as fs from 'fs/promises';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
import { exec } from 'child_process';
 | 
			
		||||
import { promisify } from 'util';
 | 
			
		||||
import { Dirent } from 'fs';
 | 
			
		||||
 | 
			
		||||
const execAsync = promisify(exec);
 | 
			
		||||
 | 
			
		||||
// Configuration
 | 
			
		||||
const FILE_EXTENSIONS = ['.md', '.png', '.jpg', '.jpeg', '.gif', '.svg'] as const;
 | 
			
		||||
const README_PATTERN = /^README(?:[-.](.+))?\.md$/;
 | 
			
		||||
 | 
			
		||||
interface SyncConfig {
 | 
			
		||||
  mainRepoPath: string;
 | 
			
		||||
  wikiPath: string;
 | 
			
		||||
  docsPath: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert markdown to GitHub Wiki format
 | 
			
		||||
 * - Images:  → [[image.png]]
 | 
			
		||||
 * - Links: [text](page.md) → [[text|page]]
 | 
			
		||||
 */
 | 
			
		||||
async function convertToWikiFormat(wikiDir: string): Promise<void> {
 | 
			
		||||
  console.log('Converting to GitHub Wiki format...');
 | 
			
		||||
  const mdFiles = await findFiles(wikiDir, ['.md']);
 | 
			
		||||
  let convertedCount = 0;
 | 
			
		||||
  
 | 
			
		||||
  for (const file of mdFiles) {
 | 
			
		||||
    let content = await fs.readFile(file, 'utf-8');
 | 
			
		||||
    const originalContent = content;
 | 
			
		||||
    
 | 
			
		||||
    // Convert image references to wiki format
 | 
			
		||||
    //  → [[image.png]]
 | 
			
		||||
    content = content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
 | 
			
		||||
      // Skip external URLs
 | 
			
		||||
      if (src.startsWith('http://') || src.startsWith('https://')) {
 | 
			
		||||
        return match;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Decode URL encoding
 | 
			
		||||
      let imagePath = src;
 | 
			
		||||
      if (src.includes('%')) {
 | 
			
		||||
        try {
 | 
			
		||||
          imagePath = decodeURIComponent(src);
 | 
			
		||||
        } catch {
 | 
			
		||||
          imagePath = src;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Extract just the filename for wiki syntax
 | 
			
		||||
      const filename = path.basename(imagePath);
 | 
			
		||||
      
 | 
			
		||||
      // Use wiki syntax for images
 | 
			
		||||
      // If alt text exists, add it after pipe
 | 
			
		||||
      if (alt && alt.trim()) {
 | 
			
		||||
        return `[[${filename}|alt=${alt}]]`;
 | 
			
		||||
      } else {
 | 
			
		||||
        return `[[${filename}]]`;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Convert internal markdown links to wiki format
 | 
			
		||||
    // [text](../path/to/Page.md) → [[text|Page]]
 | 
			
		||||
    content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, href) => {
 | 
			
		||||
      // Skip external URLs, anchors, and images
 | 
			
		||||
      if (href.startsWith('http://') || 
 | 
			
		||||
          href.startsWith('https://') || 
 | 
			
		||||
          href.startsWith('#') ||
 | 
			
		||||
          href.match(/\.(png|jpg|jpeg|gif|svg)$/i)) {
 | 
			
		||||
        return match;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Check if it's a markdown file link
 | 
			
		||||
      if (href.endsWith('.md') || href.includes('.md#')) {
 | 
			
		||||
        // Decode URL encoding
 | 
			
		||||
        let decodedHref = href;
 | 
			
		||||
        if (href.includes('%')) {
 | 
			
		||||
          try {
 | 
			
		||||
            decodedHref = decodeURIComponent(href);
 | 
			
		||||
          } catch {
 | 
			
		||||
            decodedHref = href;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Extract page name without extension and path
 | 
			
		||||
        let pageName = decodedHref
 | 
			
		||||
          .replace(/\.md(#.*)?$/, '') // Remove .md and anchor
 | 
			
		||||
          .split('/')                 // Split by path
 | 
			
		||||
          .pop() || '';               // Get last part (filename)
 | 
			
		||||
        
 | 
			
		||||
        // Convert spaces to hyphens (GitHub wiki convention)
 | 
			
		||||
        pageName = pageName.replace(/ /g, '-');
 | 
			
		||||
        
 | 
			
		||||
        // Use wiki link syntax
 | 
			
		||||
        if (text === pageName || text === pageName.replace(/-/g, ' ')) {
 | 
			
		||||
          return `[[${pageName}]]`;
 | 
			
		||||
        } else {
 | 
			
		||||
          return `[[${text}|${pageName}]]`;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // For other internal links, just decode URL encoding
 | 
			
		||||
      if (href.includes('%') && !href.startsWith('http')) {
 | 
			
		||||
        try {
 | 
			
		||||
          const decodedHref = decodeURIComponent(href);
 | 
			
		||||
          return `[${text}](${decodedHref})`;
 | 
			
		||||
        } catch {
 | 
			
		||||
          return match;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      return match;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Save if modified
 | 
			
		||||
    if (content !== originalContent) {
 | 
			
		||||
      await fs.writeFile(file, content, 'utf-8');
 | 
			
		||||
      const relativePath = path.relative(wikiDir, file);
 | 
			
		||||
      console.log(`  Converted: ${relativePath}`);
 | 
			
		||||
      convertedCount++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  if (convertedCount > 0) {
 | 
			
		||||
    console.log(`Converted ${convertedCount} files to wiki format`);
 | 
			
		||||
  } else {
 | 
			
		||||
    console.log('No files needed conversion');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Recursively find all files matching the given extensions
 | 
			
		||||
 */
 | 
			
		||||
async function findFiles(dir: string, extensions: readonly string[]): Promise<string[]> {
 | 
			
		||||
  const files: string[] = [];
 | 
			
		||||
  
 | 
			
		||||
  async function walk(currentDir: string): Promise<void> {
 | 
			
		||||
    const entries: Dirent[] = await fs.readdir(currentDir, { withFileTypes: true });
 | 
			
		||||
    
 | 
			
		||||
    for (const entry of entries) {
 | 
			
		||||
      const fullPath = path.join(currentDir, entry.name);
 | 
			
		||||
      
 | 
			
		||||
      if (entry.isDirectory()) {
 | 
			
		||||
        await walk(fullPath);
 | 
			
		||||
      } else if (entry.isFile()) {
 | 
			
		||||
        const ext = path.extname(entry.name).toLowerCase();
 | 
			
		||||
        if (extensions.includes(ext)) {
 | 
			
		||||
          files.push(fullPath);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await walk(dir);
 | 
			
		||||
  return files;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get all files in a directory recursively
 | 
			
		||||
 */
 | 
			
		||||
async function getAllFiles(dir: string): Promise<Set<string>> {
 | 
			
		||||
  const files = new Set<string>();
 | 
			
		||||
  
 | 
			
		||||
  async function walk(currentDir: string): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      const entries = await fs.readdir(currentDir, { withFileTypes: true });
 | 
			
		||||
      
 | 
			
		||||
      for (const entry of entries) {
 | 
			
		||||
        const fullPath = path.join(currentDir, entry.name);
 | 
			
		||||
        const relativePath = path.relative(dir, fullPath);
 | 
			
		||||
        
 | 
			
		||||
        // Skip .git directory
 | 
			
		||||
        if (entry.name === '.git' || relativePath.startsWith('.git')) continue;
 | 
			
		||||
        
 | 
			
		||||
        if (entry.isDirectory()) {
 | 
			
		||||
          await walk(fullPath);
 | 
			
		||||
        } else if (entry.isFile()) {
 | 
			
		||||
          files.add(relativePath);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      // Directory might not exist yet
 | 
			
		||||
      if ((error as any).code !== 'ENOENT') {
 | 
			
		||||
        throw error;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await walk(dir);
 | 
			
		||||
  return files;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Flatten directory structure - move all files to root
 | 
			
		||||
 * GitHub Wiki prefers flat structure
 | 
			
		||||
 */
 | 
			
		||||
async function flattenStructure(wikiDir: string): Promise<void> {
 | 
			
		||||
  console.log('Flattening directory structure for wiki...');
 | 
			
		||||
  const allFiles = await getAllFiles(wikiDir);
 | 
			
		||||
  let movedCount = 0;
 | 
			
		||||
  
 | 
			
		||||
  for (const file of allFiles) {
 | 
			
		||||
    // Skip if already at root
 | 
			
		||||
    if (!file.includes('/')) continue;
 | 
			
		||||
    
 | 
			
		||||
    const oldPath = path.join(wikiDir, file);
 | 
			
		||||
    const basename = path.basename(file);
 | 
			
		||||
    
 | 
			
		||||
    // Create unique name if file already exists at root
 | 
			
		||||
    let newName = basename;
 | 
			
		||||
    let counter = 1;
 | 
			
		||||
    while (await fileExists(path.join(wikiDir, newName))) {
 | 
			
		||||
      const ext = path.extname(basename);
 | 
			
		||||
      const nameWithoutExt = basename.slice(0, -ext.length);
 | 
			
		||||
      newName = `${nameWithoutExt}-${counter}${ext}`;
 | 
			
		||||
      counter++;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const newPath = path.join(wikiDir, newName);
 | 
			
		||||
    
 | 
			
		||||
    // Move file to root
 | 
			
		||||
    await fs.rename(oldPath, newPath);
 | 
			
		||||
    console.log(`  Moved: ${file} → ${newName}`);
 | 
			
		||||
    movedCount++;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  if (movedCount > 0) {
 | 
			
		||||
    console.log(`Moved ${movedCount} files to root`);
 | 
			
		||||
    
 | 
			
		||||
    // Clean up empty directories
 | 
			
		||||
    await cleanEmptyDirectories(wikiDir);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function fileExists(path: string): Promise<boolean> {
 | 
			
		||||
  try {
 | 
			
		||||
    await fs.access(path);
 | 
			
		||||
    return true;
 | 
			
		||||
  } catch {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove empty directories recursively
 | 
			
		||||
 */
 | 
			
		||||
async function cleanEmptyDirectories(dir: string): Promise<void> {
 | 
			
		||||
  const allDirs = await getAllDirectories(dir);
 | 
			
		||||
  
 | 
			
		||||
  for (const subDir of allDirs) {
 | 
			
		||||
    try {
 | 
			
		||||
      const entries = await fs.readdir(subDir);
 | 
			
		||||
      if (entries.length === 0 || (entries.length === 1 && entries[0] === '.git')) {
 | 
			
		||||
        await fs.rmdir(subDir);
 | 
			
		||||
      }
 | 
			
		||||
    } catch {
 | 
			
		||||
      // Ignore errors
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get all directories recursively
 | 
			
		||||
 */
 | 
			
		||||
async function getAllDirectories(dir: string): Promise<string[]> {
 | 
			
		||||
  const dirs: string[] = [];
 | 
			
		||||
  
 | 
			
		||||
  async function walk(currentDir: string): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      const entries = await fs.readdir(currentDir, { withFileTypes: true });
 | 
			
		||||
      
 | 
			
		||||
      for (const entry of entries) {
 | 
			
		||||
        if (entry.isDirectory() && entry.name !== '.git') {
 | 
			
		||||
          const fullPath = path.join(currentDir, entry.name);
 | 
			
		||||
          dirs.push(fullPath);
 | 
			
		||||
          await walk(fullPath);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch {
 | 
			
		||||
      // Ignore errors
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await walk(dir);
 | 
			
		||||
  return dirs.sort((a, b) => b.length - a.length); // Sort longest first for cleanup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sync files from source to wiki
 | 
			
		||||
 */
 | 
			
		||||
async function syncFiles(sourceDir: string, wikiDir: string): Promise<void> {
 | 
			
		||||
  console.log('Syncing files to wiki...');
 | 
			
		||||
  
 | 
			
		||||
  // Get all valid source files
 | 
			
		||||
  const sourceFiles = await findFiles(sourceDir, FILE_EXTENSIONS);
 | 
			
		||||
  const sourceRelativePaths = new Set<string>();
 | 
			
		||||
  
 | 
			
		||||
  // Copy all source files
 | 
			
		||||
  console.log(`Found ${sourceFiles.length} files to sync`);
 | 
			
		||||
  
 | 
			
		||||
  for (const file of sourceFiles) {
 | 
			
		||||
    const relativePath = path.relative(sourceDir, file);
 | 
			
		||||
    sourceRelativePaths.add(relativePath);
 | 
			
		||||
    
 | 
			
		||||
    const targetPath = path.join(wikiDir, relativePath);
 | 
			
		||||
    const targetDir = path.dirname(targetPath);
 | 
			
		||||
    
 | 
			
		||||
    // Create directory structure
 | 
			
		||||
    await fs.mkdir(targetDir, { recursive: true });
 | 
			
		||||
    
 | 
			
		||||
    // Copy file
 | 
			
		||||
    await fs.copyFile(file, targetPath);
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  // Remove orphaned files
 | 
			
		||||
  const wikiFiles = await getAllFiles(wikiDir);
 | 
			
		||||
  for (const wikiFile of wikiFiles) {
 | 
			
		||||
    if (!sourceRelativePaths.has(wikiFile) && !wikiFile.startsWith('Home')) {
 | 
			
		||||
      const fullPath = path.join(wikiDir, wikiFile);
 | 
			
		||||
      await fs.unlink(fullPath);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Copy root README.md to wiki as Home.md if it exists
 | 
			
		||||
 */
 | 
			
		||||
async function copyRootReadme(mainRepoPath: string, wikiPath: string): Promise<void> {
 | 
			
		||||
  const rootReadmePath = path.join(mainRepoPath, 'README.md');
 | 
			
		||||
  const wikiHomePath = path.join(wikiPath, 'Home.md');
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    await fs.access(rootReadmePath);
 | 
			
		||||
    await fs.copyFile(rootReadmePath, wikiHomePath);
 | 
			
		||||
    console.log('  Copied root README.md as Home.md');
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.log('  No root README.md found to use as Home page');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rename README files to wiki-compatible names
 | 
			
		||||
 */
 | 
			
		||||
async function renameReadmeFiles(wikiDir: string): Promise<void> {
 | 
			
		||||
  console.log('Converting README files for wiki compatibility...');
 | 
			
		||||
  const files = await fs.readdir(wikiDir);
 | 
			
		||||
  
 | 
			
		||||
  for (const file of files) {
 | 
			
		||||
    const match = file.match(README_PATTERN);
 | 
			
		||||
    if (match) {
 | 
			
		||||
      const oldPath = path.join(wikiDir, file);
 | 
			
		||||
      let newName: string;
 | 
			
		||||
      
 | 
			
		||||
      if (match[1]) {
 | 
			
		||||
        // Language-specific README
 | 
			
		||||
        newName = `Home-${match[1]}.md`;
 | 
			
		||||
      } else {
 | 
			
		||||
        // Main README
 | 
			
		||||
        newName = 'Home.md';
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      const newPath = path.join(wikiDir, newName);
 | 
			
		||||
      await fs.rename(oldPath, newPath);
 | 
			
		||||
      console.log(`  Renamed: ${file} → ${newName}`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if there are any changes in the wiki
 | 
			
		||||
 */
 | 
			
		||||
async function hasChanges(wikiDir: string): Promise<boolean> {
 | 
			
		||||
  try {
 | 
			
		||||
    const { stdout } = await execAsync('git status --porcelain', { cwd: wikiDir });
 | 
			
		||||
    return stdout.trim().length > 0;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Error checking git status:', error);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get configuration from environment variables
 | 
			
		||||
 */
 | 
			
		||||
function getConfig(): SyncConfig {
 | 
			
		||||
  const mainRepoPath = process.env.MAIN_REPO_PATH || 'main-repo';
 | 
			
		||||
  const wikiPath = process.env.WIKI_PATH || 'wiki';
 | 
			
		||||
  const docsPath = path.join(mainRepoPath, 'docs');
 | 
			
		||||
  
 | 
			
		||||
  return { mainRepoPath, wikiPath, docsPath };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Main sync function
 | 
			
		||||
 */
 | 
			
		||||
async function syncDocsToWiki(): Promise<void> {
 | 
			
		||||
  const config = getConfig();
 | 
			
		||||
  const flattenWiki = process.env.FLATTEN_WIKI === 'true';
 | 
			
		||||
  
 | 
			
		||||
  console.log('Starting documentation sync to wiki...');
 | 
			
		||||
  console.log(`Source: ${config.docsPath}`);
 | 
			
		||||
  console.log(`Target: ${config.wikiPath}`);
 | 
			
		||||
  console.log(`Flatten structure: ${flattenWiki}`);
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    // Verify paths exist
 | 
			
		||||
    await fs.access(config.docsPath);
 | 
			
		||||
    await fs.access(config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Sync files
 | 
			
		||||
    await syncFiles(config.docsPath, config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Copy root README.md as Home.md
 | 
			
		||||
    await copyRootReadme(config.mainRepoPath, config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Convert to wiki format
 | 
			
		||||
    await convertToWikiFormat(config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Optionally flatten directory structure
 | 
			
		||||
    if (flattenWiki) {
 | 
			
		||||
      await flattenStructure(config.wikiPath);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Rename README files to wiki-compatible names
 | 
			
		||||
    await renameReadmeFiles(config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Check for changes
 | 
			
		||||
    const changed = await hasChanges(config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    if (changed) {
 | 
			
		||||
      console.log('\nChanges detected in wiki');
 | 
			
		||||
      process.stdout.write('::set-output name=changes::true\n');
 | 
			
		||||
    } else {
 | 
			
		||||
      console.log('\nNo changes detected in wiki');
 | 
			
		||||
      process.stdout.write('::set-output name=changes::false\n');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    console.log('Sync completed successfully!');
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Error during sync:', error);
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run if called directly
 | 
			
		||||
if (require.main === module) {
 | 
			
		||||
  syncDocsToWiki();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { syncDocsToWiki };
 | 
			
		||||
							
								
								
									
										437
									
								
								.github/scripts/sync-docs-to-wiki.ts
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										437
									
								
								.github/scripts/sync-docs-to-wiki.ts
									
									
									
									
										vendored
									
									
										Executable file
									
								
							@@ -0,0 +1,437 @@
 | 
			
		||||
#!/usr/bin/env tsx
 | 
			
		||||
 | 
			
		||||
import * as fs from 'fs/promises';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
import { exec } from 'child_process';
 | 
			
		||||
import { promisify } from 'util';
 | 
			
		||||
import { Dirent } from 'fs';
 | 
			
		||||
 | 
			
		||||
const execAsync = promisify(exec);
 | 
			
		||||
 | 
			
		||||
// Configuration
 | 
			
		||||
const FILE_EXTENSIONS = ['.md', '.png', '.jpg', '.jpeg', '.gif', '.svg'] as const;
 | 
			
		||||
const README_PATTERN = /^README(?:[-.](.+))?\.md$/;
 | 
			
		||||
 | 
			
		||||
interface SyncConfig {
 | 
			
		||||
  mainRepoPath: string;
 | 
			
		||||
  wikiPath: string;
 | 
			
		||||
  docsPath: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Recursively find all files matching the given extensions
 | 
			
		||||
 */
 | 
			
		||||
async function findFiles(dir: string, extensions: readonly string[]): Promise<string[]> {
 | 
			
		||||
  const files: string[] = [];
 | 
			
		||||
  
 | 
			
		||||
  async function walk(currentDir: string): Promise<void> {
 | 
			
		||||
    const entries: Dirent[] = await fs.readdir(currentDir, { withFileTypes: true });
 | 
			
		||||
    
 | 
			
		||||
    for (const entry of entries) {
 | 
			
		||||
      const fullPath = path.join(currentDir, entry.name);
 | 
			
		||||
      
 | 
			
		||||
      if (entry.isDirectory()) {
 | 
			
		||||
        await walk(fullPath);
 | 
			
		||||
      } else if (entry.isFile()) {
 | 
			
		||||
        const ext = path.extname(entry.name).toLowerCase();
 | 
			
		||||
        if (extensions.includes(ext)) {
 | 
			
		||||
          files.push(fullPath);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await walk(dir);
 | 
			
		||||
  return files;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get all files in a directory recursively
 | 
			
		||||
 */
 | 
			
		||||
async function getAllFiles(dir: string): Promise<Set<string>> {
 | 
			
		||||
  const files = new Set<string>();
 | 
			
		||||
  
 | 
			
		||||
  async function walk(currentDir: string): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      const entries = await fs.readdir(currentDir, { withFileTypes: true });
 | 
			
		||||
      
 | 
			
		||||
      for (const entry of entries) {
 | 
			
		||||
        const fullPath = path.join(currentDir, entry.name);
 | 
			
		||||
        const relativePath = path.relative(dir, fullPath);
 | 
			
		||||
        
 | 
			
		||||
        // Skip .git directory
 | 
			
		||||
        if (entry.name === '.git' || relativePath.startsWith('.git')) continue;
 | 
			
		||||
        
 | 
			
		||||
        if (entry.isDirectory()) {
 | 
			
		||||
          await walk(fullPath);
 | 
			
		||||
        } else if (entry.isFile()) {
 | 
			
		||||
          files.add(relativePath);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      // Directory might not exist yet
 | 
			
		||||
      if ((error as any).code !== 'ENOENT') {
 | 
			
		||||
        throw error;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await walk(dir);
 | 
			
		||||
  return files;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sync files from source to wiki, preserving directory structure and removing orphaned files
 | 
			
		||||
 */
 | 
			
		||||
async function syncFiles(sourceDir: string, wikiDir: string): Promise<void> {
 | 
			
		||||
  console.log('Analyzing files to sync...');
 | 
			
		||||
  
 | 
			
		||||
  // Get all valid source files
 | 
			
		||||
  const sourceFiles = await findFiles(sourceDir, FILE_EXTENSIONS);
 | 
			
		||||
  const sourceRelativePaths = new Set<string>();
 | 
			
		||||
  
 | 
			
		||||
  // Copy all source files and track their paths
 | 
			
		||||
  console.log(`Found ${sourceFiles.length} files to sync`);
 | 
			
		||||
  let copiedCount = 0;
 | 
			
		||||
  let skippedCount = 0;
 | 
			
		||||
  
 | 
			
		||||
  for (const file of sourceFiles) {
 | 
			
		||||
    const relativePath = path.relative(sourceDir, file);
 | 
			
		||||
    sourceRelativePaths.add(relativePath);
 | 
			
		||||
    
 | 
			
		||||
    const targetPath = path.join(wikiDir, relativePath);
 | 
			
		||||
    const targetDir = path.dirname(targetPath);
 | 
			
		||||
    
 | 
			
		||||
    // Create directory structure
 | 
			
		||||
    await fs.mkdir(targetDir, { recursive: true });
 | 
			
		||||
    
 | 
			
		||||
    // Check if file needs updating (compare modification times)
 | 
			
		||||
    let needsCopy = true;
 | 
			
		||||
    try {
 | 
			
		||||
      const sourceStat = await fs.stat(file);
 | 
			
		||||
      const targetStat = await fs.stat(targetPath);
 | 
			
		||||
      // Only copy if source is newer or sizes differ
 | 
			
		||||
      needsCopy = sourceStat.mtime > targetStat.mtime || sourceStat.size !== targetStat.size;
 | 
			
		||||
    } catch {
 | 
			
		||||
      // Target doesn't exist, needs copy
 | 
			
		||||
      needsCopy = true;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (needsCopy) {
 | 
			
		||||
      await fs.copyFile(file, targetPath);
 | 
			
		||||
      console.log(`  Updated: ${relativePath}`);
 | 
			
		||||
      copiedCount++;
 | 
			
		||||
    } else {
 | 
			
		||||
      skippedCount++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  console.log(`Updated ${copiedCount} files, ${skippedCount} unchanged`);
 | 
			
		||||
  
 | 
			
		||||
  // Find and remove files that don't exist in source
 | 
			
		||||
  console.log('Checking for orphaned files in wiki...');
 | 
			
		||||
  const wikiFiles = await getAllFiles(wikiDir);
 | 
			
		||||
  let removedCount = 0;
 | 
			
		||||
  
 | 
			
		||||
  for (const wikiFile of wikiFiles) {
 | 
			
		||||
    // Check if this file should exist (either as-is or will be renamed)
 | 
			
		||||
    let shouldExist = sourceRelativePaths.has(wikiFile);
 | 
			
		||||
    
 | 
			
		||||
    // Special handling for Home files that will be created from READMEs
 | 
			
		||||
    if (wikiFile.startsWith('Home')) {
 | 
			
		||||
      const readmeVariant1 = wikiFile.replace(/^Home(-.*)?\.md$/, 'README$1.md');
 | 
			
		||||
      const readmeVariant2 = wikiFile.replace(/^Home-(.+)\.md$/, 'README.$1.md');
 | 
			
		||||
      shouldExist = sourceRelativePaths.has(readmeVariant1) || sourceRelativePaths.has(readmeVariant2) || sourceRelativePaths.has('README.md');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (!shouldExist) {
 | 
			
		||||
      const fullPath = path.join(wikiDir, wikiFile);
 | 
			
		||||
      await fs.unlink(fullPath);
 | 
			
		||||
      console.log(`  Removed: ${wikiFile}`);
 | 
			
		||||
      removedCount++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  if (removedCount > 0) {
 | 
			
		||||
    console.log(`Removed ${removedCount} orphaned files`);
 | 
			
		||||
    
 | 
			
		||||
    // Clean up empty directories
 | 
			
		||||
    await cleanEmptyDirectories(wikiDir);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Remove empty directories recursively
 | 
			
		||||
 */
 | 
			
		||||
async function cleanEmptyDirectories(dir: string): Promise<void> {
 | 
			
		||||
  async function removeEmptyDirs(currentDir: string): Promise<boolean> {
 | 
			
		||||
    if (currentDir === dir) return false; // Don't remove root
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      const entries = await fs.readdir(currentDir, { withFileTypes: true });
 | 
			
		||||
      
 | 
			
		||||
      // Skip .git directory
 | 
			
		||||
      const filteredEntries = entries.filter(e => e.name !== '.git');
 | 
			
		||||
      
 | 
			
		||||
      if (filteredEntries.length === 0) {
 | 
			
		||||
        await fs.rmdir(currentDir);
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Check subdirectories
 | 
			
		||||
      for (const entry of filteredEntries) {
 | 
			
		||||
        if (entry.isDirectory()) {
 | 
			
		||||
          const subDir = path.join(currentDir, entry.name);
 | 
			
		||||
          await removeEmptyDirs(subDir);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Check again after cleaning subdirectories
 | 
			
		||||
      const remainingEntries = await fs.readdir(currentDir);
 | 
			
		||||
      const filteredRemaining = remainingEntries.filter(e => e !== '.git');
 | 
			
		||||
      
 | 
			
		||||
      if (filteredRemaining.length === 0 && currentDir !== dir) {
 | 
			
		||||
        await fs.rmdir(currentDir);
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      return false;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  // Get all directories and process them
 | 
			
		||||
  const allDirs = await getAllDirectories(dir);
 | 
			
		||||
  for (const subDir of allDirs) {
 | 
			
		||||
    await removeEmptyDirs(subDir);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get all directories recursively
 | 
			
		||||
 */
 | 
			
		||||
async function getAllDirectories(dir: string): Promise<string[]> {
 | 
			
		||||
  const dirs: string[] = [];
 | 
			
		||||
  
 | 
			
		||||
  async function walk(currentDir: string): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      const entries = await fs.readdir(currentDir, { withFileTypes: true });
 | 
			
		||||
      
 | 
			
		||||
      for (const entry of entries) {
 | 
			
		||||
        if (entry.isDirectory() && entry.name !== '.git') {
 | 
			
		||||
          const fullPath = path.join(currentDir, entry.name);
 | 
			
		||||
          dirs.push(fullPath);
 | 
			
		||||
          await walk(fullPath);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch {
 | 
			
		||||
      // Ignore errors
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  await walk(dir);
 | 
			
		||||
  return dirs.sort((a, b) => b.length - a.length); // Sort longest first for cleanup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Fix references in markdown files for wiki compatibility
 | 
			
		||||
 * 
 | 
			
		||||
 * Issues fixed:
 | 
			
		||||
 * 1. URL-encoded image references (spaces as %20) need to match actual filenames
 | 
			
		||||
 * 2. Internal markdown links need to be converted to wiki syntax
 | 
			
		||||
 * 3. Images can optionally use wiki syntax [[image.png]] for better compatibility
 | 
			
		||||
 */
 | 
			
		||||
async function fixImageReferences(wikiDir: string): Promise<void> {
 | 
			
		||||
  console.log('Fixing references for GitHub Wiki compatibility...');
 | 
			
		||||
  const mdFiles = await findFiles(wikiDir, ['.md']);
 | 
			
		||||
  let fixedCount = 0;
 | 
			
		||||
  
 | 
			
		||||
  for (const file of mdFiles) {
 | 
			
		||||
    let content = await fs.readFile(file, 'utf-8');
 | 
			
		||||
    let modified = false;
 | 
			
		||||
    const originalContent = content;
 | 
			
		||||
    
 | 
			
		||||
    // Step 1: Fix URL-encoded image references
 | 
			
		||||
    // Convert  to 
 | 
			
		||||
    content = content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
 | 
			
		||||
      // Skip external URLs
 | 
			
		||||
      if (src.startsWith('http://') || src.startsWith('https://')) {
 | 
			
		||||
        return match;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Decode URL encoding if present
 | 
			
		||||
      if (src.includes('%')) {
 | 
			
		||||
        try {
 | 
			
		||||
          const decodedSrc = decodeURIComponent(src);
 | 
			
		||||
          return ``;
 | 
			
		||||
        } catch {
 | 
			
		||||
          return match;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      return match;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Step 2: Fix internal links - decode URL encoding but keep standard markdown format
 | 
			
		||||
    // GitHub Wiki actually supports standard markdown links with relative paths
 | 
			
		||||
    content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, href) => {
 | 
			
		||||
      // Skip external URLs, anchors, and images
 | 
			
		||||
      if (href.startsWith('http://') || 
 | 
			
		||||
          href.startsWith('https://') || 
 | 
			
		||||
          href.startsWith('#') ||
 | 
			
		||||
          href.match(/\.(png|jpg|jpeg|gif|svg)$/i)) {
 | 
			
		||||
        return match;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      // Decode URL encoding for all internal links
 | 
			
		||||
      if (href.includes('%')) {
 | 
			
		||||
        try {
 | 
			
		||||
          const decodedHref = decodeURIComponent(href);
 | 
			
		||||
          return `[${text}](${decodedHref})`;
 | 
			
		||||
        } catch {
 | 
			
		||||
          return match;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      return match;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Check if content was modified
 | 
			
		||||
    if (content !== originalContent) {
 | 
			
		||||
      modified = true;
 | 
			
		||||
      await fs.writeFile(file, content, 'utf-8');
 | 
			
		||||
      const relativePath = path.relative(wikiDir, file);
 | 
			
		||||
      console.log(`  Fixed references in: ${relativePath}`);
 | 
			
		||||
      fixedCount++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  if (fixedCount > 0) {
 | 
			
		||||
    console.log(`Fixed references in ${fixedCount} files`);
 | 
			
		||||
  } else {
 | 
			
		||||
    console.log('No references needed fixing');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rename README files to wiki-compatible names
 | 
			
		||||
 */
 | 
			
		||||
async function renameReadmeFiles(wikiDir: string): Promise<void> {
 | 
			
		||||
  console.log('Converting README files for wiki compatibility...');
 | 
			
		||||
  const files = await fs.readdir(wikiDir);
 | 
			
		||||
  
 | 
			
		||||
  for (const file of files) {
 | 
			
		||||
    const match = file.match(README_PATTERN);
 | 
			
		||||
    if (match) {
 | 
			
		||||
      const oldPath = path.join(wikiDir, file);
 | 
			
		||||
      let newName: string;
 | 
			
		||||
      
 | 
			
		||||
      if (match[1]) {
 | 
			
		||||
        // Language-specific README (e.g., README-ZH_CN.md or README.es.md)
 | 
			
		||||
        newName = `Home-${match[1]}.md`;
 | 
			
		||||
      } else {
 | 
			
		||||
        // Main README
 | 
			
		||||
        newName = 'Home.md';
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      const newPath = path.join(wikiDir, newName);
 | 
			
		||||
      await fs.rename(oldPath, newPath);
 | 
			
		||||
      console.log(`  Renamed: ${file} → ${newName}`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if there are any changes in the wiki
 | 
			
		||||
 */
 | 
			
		||||
async function hasChanges(wikiDir: string): Promise<boolean> {
 | 
			
		||||
  try {
 | 
			
		||||
    const { stdout } = await execAsync('git status --porcelain', { cwd: wikiDir });
 | 
			
		||||
    return stdout.trim().length > 0;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Error checking git status:', error);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Copy root README.md to wiki as Home.md if it exists
 | 
			
		||||
 */
 | 
			
		||||
async function copyRootReadme(mainRepoPath: string, wikiPath: string): Promise<void> {
 | 
			
		||||
  const rootReadmePath = path.join(mainRepoPath, 'README.md');
 | 
			
		||||
  const wikiHomePath = path.join(wikiPath, 'Home.md');
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    await fs.access(rootReadmePath);
 | 
			
		||||
    await fs.copyFile(rootReadmePath, wikiHomePath);
 | 
			
		||||
    console.log('  Copied root README.md as Home.md');
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    // Root README doesn't exist or can't be accessed
 | 
			
		||||
    console.log('  No root README.md found to use as Home page');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get configuration from environment variables
 | 
			
		||||
 */
 | 
			
		||||
function getConfig(): SyncConfig {
 | 
			
		||||
  const mainRepoPath = process.env.MAIN_REPO_PATH || 'main-repo';
 | 
			
		||||
  const wikiPath = process.env.WIKI_PATH || 'wiki';
 | 
			
		||||
  const docsPath = path.join(mainRepoPath, 'docs');
 | 
			
		||||
  
 | 
			
		||||
  return { mainRepoPath, wikiPath, docsPath };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Main sync function
 | 
			
		||||
 */
 | 
			
		||||
async function syncDocsToWiki(): Promise<void> {
 | 
			
		||||
  const config = getConfig();
 | 
			
		||||
  
 | 
			
		||||
  console.log('Starting documentation sync to wiki...');
 | 
			
		||||
  console.log(`Source: ${config.docsPath}`);
 | 
			
		||||
  console.log(`Target: ${config.wikiPath}`);
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    // Verify paths exist
 | 
			
		||||
    await fs.access(config.docsPath);
 | 
			
		||||
    await fs.access(config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Sync files (copy new/updated, remove orphaned)
 | 
			
		||||
    await syncFiles(config.docsPath, config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Copy root README.md as Home.md
 | 
			
		||||
    await copyRootReadme(config.mainRepoPath, config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Fix image and link references for wiki compatibility
 | 
			
		||||
    await fixImageReferences(config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Rename README files to wiki-compatible names
 | 
			
		||||
    await renameReadmeFiles(config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    // Check for changes
 | 
			
		||||
    const changed = await hasChanges(config.wikiPath);
 | 
			
		||||
    
 | 
			
		||||
    if (changed) {
 | 
			
		||||
      console.log('\nChanges detected in wiki');
 | 
			
		||||
      // GitHub Actions output format
 | 
			
		||||
      process.stdout.write('::set-output name=changes::true\n');
 | 
			
		||||
    } else {
 | 
			
		||||
      console.log('\nNo changes detected in wiki');
 | 
			
		||||
      process.stdout.write('::set-output name=changes::false\n');
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    console.log('Sync completed successfully!');
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Error during sync:', error);
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run if called directly
 | 
			
		||||
if (require.main === module) {
 | 
			
		||||
  syncDocsToWiki();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { syncDocsToWiki };
 | 
			
		||||
							
								
								
									
										67
									
								
								.github/workflows/sync-docs-to-wiki.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								.github/workflows/sync-docs-to-wiki.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
name: Sync Docs to Wiki
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
    paths:
 | 
			
		||||
      - 'docs/**'
 | 
			
		||||
  workflow_dispatch: # Allow manual triggering
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read  # Read access to repository contents
 | 
			
		||||
  # Note: Writing to wiki requires a PAT or GITHUB_TOKEN with additional permissions
 | 
			
		||||
  # The default GITHUB_TOKEN cannot write to wikis, so we need to:
 | 
			
		||||
  # 1. Create a Personal Access Token (PAT) with 'repo' scope
 | 
			
		||||
  # 2. Add it as a repository secret named WIKI_TOKEN
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  sync-wiki:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout main repository
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        with:
 | 
			
		||||
          path: main-repo
 | 
			
		||||
          
 | 
			
		||||
      - name: Checkout wiki repository
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        with:
 | 
			
		||||
          repository: TriliumNext/Trilium.wiki
 | 
			
		||||
          path: wiki
 | 
			
		||||
          token: ${{ secrets.WIKI_TOKEN }}
 | 
			
		||||
          
 | 
			
		||||
      - name: Setup Node.js
 | 
			
		||||
        uses: actions/setup-node@v4
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: '20'
 | 
			
		||||
          
 | 
			
		||||
      - name: Install tsx for TypeScript execution
 | 
			
		||||
        run: npm install -g tsx
 | 
			
		||||
          
 | 
			
		||||
      - name: Setup Git
 | 
			
		||||
        run: |
 | 
			
		||||
          git config --global user.email "action@github.com"
 | 
			
		||||
          git config --global user.name "GitHub Action"
 | 
			
		||||
          
 | 
			
		||||
      - name: Sync documentation to wiki
 | 
			
		||||
        id: sync
 | 
			
		||||
        run: |
 | 
			
		||||
          tsx main-repo/.github/scripts/sync-docs-to-wiki.ts
 | 
			
		||||
        env:
 | 
			
		||||
          MAIN_REPO_PATH: main-repo
 | 
			
		||||
          WIKI_PATH: wiki
 | 
			
		||||
          
 | 
			
		||||
      - name: Commit and push changes
 | 
			
		||||
        if: contains(steps.sync.outputs.changes, 'true')
 | 
			
		||||
        run: |
 | 
			
		||||
          cd wiki
 | 
			
		||||
          git add .
 | 
			
		||||
          git commit -m "Sync documentation from main repository
 | 
			
		||||
          
 | 
			
		||||
          Source commit: ${{ github.sha }}
 | 
			
		||||
          Triggered by: ${{ github.event.head_commit.message }}"
 | 
			
		||||
          git push
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.WIKI_TOKEN }}
 | 
			
		||||
		Reference in New Issue
	
	Block a user