@rayners/foundry-dev-tools
Version:
Shared development tools and configurations for FoundryVTT modules
198 lines (172 loc) • 6.31 kB
JavaScript
/**
* Link or copy a Foundry module to a Foundry VTT installation for testing
*
* Usage:
* node ../foundry-dev-tools/scripts/link-module.js /path/to/foundry/Data/modules
* node ../foundry-dev-tools/scripts/link-module.js --copy /path/to/foundry/Data/modules
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
function showUsage() {
console.log('Foundry Module Linker');
console.log('====================');
console.log('');
console.log('Usage:');
console.log(' node ../foundry-dev-tools/scripts/link-module.js [options] /path/to/foundry/Data/modules');
console.log('');
console.log('Options:');
console.log(' --copy Copy files instead of creating symlinks');
console.log(' --help Show this help message');
console.log('');
console.log('Examples:');
console.log(' # Link to Electron Foundry (macOS)');
console.log(' node ../foundry-dev-tools/scripts/link-module.js ~/Library/Application\\ Support/FoundryVTT/Data/modules');
console.log('');
console.log(' # Link to Node.js Foundry');
console.log(' node ../foundry-dev-tools/scripts/link-module.js /path/to/foundrydata/modules');
console.log('');
console.log(' # Copy instead of link (safer for some setups)');
console.log(' node ../foundry-dev-tools/scripts/link-module.js --copy /path/to/foundrydata/modules');
console.log('');
console.log('Common Foundry Data Paths:');
console.log(' macOS Electron: ~/Library/Application Support/FoundryVTT/Data/modules');
console.log(' Windows Electron: %LOCALAPPDATA%/FoundryVTT/Data/modules');
console.log(' Linux Electron: ~/.local/share/FoundryVTT/Data/modules');
}
function getModuleInfo(projectRoot) {
// Try to read module.json
const moduleJsonPath = path.join(projectRoot, 'module.json');
if (!fs.existsSync(moduleJsonPath)) {
console.error('❌ No module.json found in current directory');
console.log('💡 Make sure you run this from a Foundry module directory');
process.exit(1);
}
const moduleJson = JSON.parse(fs.readFileSync(moduleJsonPath, 'utf8'));
return {
id: moduleJson.id || moduleJson.name,
name: moduleJson.title || moduleJson.id || moduleJson.name
};
}
function copyRecursive(src, dest, excludePatterns = []) {
const basename = path.basename(src);
// Check if this file/directory should be excluded
for (const pattern of excludePatterns) {
if (basename === pattern || src.endsWith(pattern)) {
return;
}
}
const stat = fs.statSync(src);
if (stat.isDirectory()) {
fs.mkdirSync(dest, { recursive: true });
const files = fs.readdirSync(src);
for (const file of files) {
copyRecursive(
path.join(src, file),
path.join(dest, file),
excludePatterns
);
}
} else {
fs.copyFileSync(src, dest);
}
}
function linkModule(foundryModulesPath, useCopy = false) {
const projectRoot = process.cwd();
const moduleInfo = getModuleInfo(projectRoot);
console.log(`🔧 Linking module: ${moduleInfo.name} (${moduleInfo.id})`);
// Ensure the foundry modules directory exists
if (!fs.existsSync(foundryModulesPath)) {
console.error(`❌ Foundry modules directory not found: ${foundryModulesPath}`);
console.log('💡 Make sure Foundry VTT is installed and the path is correct');
process.exit(1);
}
const targetPath = path.join(foundryModulesPath, moduleInfo.id);
// Remove existing link/directory
if (fs.existsSync(targetPath)) {
console.log(`🗑️ Removing existing module at ${targetPath}`);
fs.rmSync(targetPath, { recursive: true, force: true });
}
if (useCopy) {
console.log(`📂 Copying module to ${targetPath}`);
// Exclude development files when copying
const excludePatterns = [
'node_modules',
'.git',
'.gitignore',
'src',
'test',
'scripts',
'README.md',
'CONTRIBUTING.md',
'CLAUDE.md',
'tsconfig.json',
'vitest.config.ts',
'rollup.config.js',
'package.json',
'package-lock.json',
'.eslintrc.json',
'eslint.config.js',
'prettier.config.js'
];
copyRecursive(projectRoot, targetPath, excludePatterns);
} else {
console.log(`🔗 Creating symlink from ${projectRoot} to ${targetPath}`);
try {
fs.symlinkSync(projectRoot, targetPath, 'dir');
} catch (error) {
if (error.code === 'EPERM') {
console.error('❌ Permission denied creating symlink.');
console.log('💡 Try running with elevated permissions or use --copy flag');
process.exit(1);
}
if (error.code === 'EEXIST') {
console.error('❌ Target already exists (this should not happen)');
process.exit(1);
}
throw error;
}
}
console.log('✅ Module linked successfully!');
console.log('');
console.log('Next steps:');
console.log('1. Start/restart Foundry VTT');
console.log('2. Create or open a test world');
console.log(`3. Enable the "${moduleInfo.name}" module in Module Management`);
console.log('4. Test the module functionality');
console.log('');
if (!useCopy) {
console.log('💡 Development mode (symlink):');
console.log(' - Changes to templates, styles, and dist/ are reflected immediately');
console.log(' - Run your build command after making source changes');
console.log(' - F5 refresh in Foundry to see changes');
} else {
console.log('💡 Copy mode:');
console.log(' - Run this script again after making changes');
console.log(' - Foundry may need restart for some changes');
}
}
// Parse command line arguments
const args = process.argv.slice(2);
let foundryPath = null;
let useCopy = false;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--help' || arg === '-h') {
showUsage();
process.exit(0);
} else if (arg === '--copy') {
useCopy = true;
} else if (!foundryPath && !arg.startsWith('--')) {
foundryPath = path.resolve(arg);
}
}
if (!foundryPath) {
console.error('❌ No Foundry modules path provided');
console.log('');
showUsage();
process.exit(1);
}
linkModule(foundryPath, useCopy);