From ba738244abbe118f02839c61acadc1fd046d1b8e Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 5 Jun 2026 20:43:37 -0400 Subject: [PATCH] Fancier dev setup --- .env.example | 2 +- README.md | 12 +++--- package.json | 1 - tools/create-symlink.mjs | 91 +++++++++++++++++++++++++--------------- tools/dev-setup.mjs | 18 -------- tools/run-start.mjs | 17 ++------ tools/util.mjs | 39 +++++++++++++++++ 7 files changed, 107 insertions(+), 73 deletions(-) delete mode 100644 tools/dev-setup.mjs create mode 100644 tools/util.mjs diff --git a/.env.example b/.env.example index 730309d3..3d62d699 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ FOUNDRY_MAIN_PATH=/path/to/foundry/resources/app/main.js -FOUNDRY_DATA_PATH=/path/to/foundry/data \ No newline at end of file +FOUNDRY_DATA_PATH=/path/to/foundry/ \ No newline at end of file diff --git a/README.md b/README.md index ac3666b3..177636c7 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,13 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart 3. **Configure your Foundry paths:** - ```bash - npm run setup:dev -- --foundry-path="/path/to/foundry/main.js" --data-path="/path/to/data" - ``` + Copy `.env.example` to `.env` and set up the foundry path and data path. The foundry path must point to main.js, and the data path must point the folder containing the Data folder itself (but not the `Data` folder itself). -4. **Start developing:** +4. **Setup symlinks:** + + In a shell with elevated privilages, run `npm run createSymlink`. It will add a foundry folder for types, and add a symlink to the daggerheart folder into your foundry data. + +5. **Start developing:** ```bash npm start ``` @@ -51,7 +53,7 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart - `npm start` - Start development with file watching and Foundry launching - `npm run build` - One-time build -- `npm run setup:dev -- --foundry-path="" --data-path=""` - Configure development environment +- `npm run createSymlink` - Setup types and output symlinks ### Notes diff --git a/package.json b/package.json index ede90401..d164261f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "pullYMLtoLDB": "node ./tools/pullYMLtoLDB.mjs", "pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDB.mjs --build", "createSymlink": "node ./tools/create-symlink.mjs", - "setup:dev": "node ./tools/dev-setup.mjs", "lint": "eslint", "lint:fix": "eslint --fix", "prepare": "husky" diff --git a/tools/create-symlink.mjs b/tools/create-symlink.mjs index 4e14d5df..a75c4729 100644 --- a/tools/create-symlink.mjs +++ b/tools/create-symlink.mjs @@ -1,47 +1,70 @@ import fs from 'fs'; import path from 'path'; import readline from 'readline'; +import { isContainedPath, readEnvFile } from './util.mjs'; -console.log('Creates a foundry symlink in the base folder for type purposes\n'); +const projectRoot = path.resolve(import.meta.dirname, '../') +const { foundryRoot, dataPath } = readEnvFile(); -const askQuestion = question => { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); +async function createFoundrySymlink() { + // If foundry already exists, exit and inform the user. This operation can't complete correctly otherwise. + // If the folder is empty, its fine. It may have failed due to perms + const foundryDestPath = path.join(projectRoot, 'foundry'); + if (fs.existsSync(foundryDestPath) && fs.readdirSync(foundryDestPath).length) { + console.log('"foundry" folder already exists in this project'); + return; + } - return new Promise(resolve => - rl.question(question, answer => { - rl.close(); - resolve(answer); - }) - ); -}; - -const installPath = await askQuestion('Enter your Foundry install path: '); - -// Determine if it's an Electron install (nested structure) -const nested = fs.existsSync(path.join(installPath, 'resources', 'app')); -const fileRoot = nested ? path.join(installPath, 'resources', 'app') : installPath; - -try { - await fs.promises.mkdir('foundry'); -} catch (e) { - if (e.code !== 'EEXIST') throw e; -} - -// JavaScript files -for (const p of ['client', 'common', 'tsconfig.json']) { + console.log('Creating "foundry" symlinks for types'); try { - await fs.promises.symlink(path.join(fileRoot, p), path.join('foundry', p)); + await fs.promises.mkdir(foundryDestPath); + console.log('Root foundry folder created'); } catch (e) { if (e.code !== 'EEXIST') throw e; } + + // JavaScript files + for (const p of ['client', 'common', 'tsconfig.json']) { + try { + await fs.promises.symlink(path.join(foundryRoot, p), path.join(foundryDestPath, p)); + console.log(`${p} folder created`); + } catch (e) { + if (e.code !== 'EEXIST') throw e; + } + } + + // Language files + try { + await fs.promises.symlink(path.join(foundryRoot, 'public', 'lang'), path.join(foundryDestPath, 'lang')); + console.log(`lang folder created`); + } catch (e) { + if (e.code !== 'EEXIST') throw e; + console.log(`lang folder already exists`); + } } -// Language files -try { - await fs.promises.symlink(path.join(fileRoot, 'public', 'lang'), path.join('foundry', 'lang')); -} catch (e) { - if (e.code !== 'EEXIST') throw e; +async function createDaggerheartSymlink() { + if (isContainedPath(dataPath, projectRoot)) { + console.log('The Daggerheart project repo is in foundry data, so a symlink won\'t be created'); + return; + } + + const destination = path.join(dataPath, 'Data', 'systems', 'daggerheart'); + if (fs.existsSync(destination)) { + console.log('A Daggerheart folder already exists in Foundry data'); + return; + } + + console.log('Creating Daggerheart symlink in the foundry systems folder') + try { + await fs.promises.symlink(projectRoot, destination); + console.log('Daggerheart system folder symlink created'); + } catch (e) { + if (e.code !== 'EEXIST') throw e; + console.log(`Daggerheart system folder already exists`); + } } + +await createFoundrySymlink(); +console.log(); +await createDaggerheartSymlink(); diff --git a/tools/dev-setup.mjs b/tools/dev-setup.mjs deleted file mode 100644 index f232f5a8..00000000 --- a/tools/dev-setup.mjs +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env node -import fs from 'fs'; - -const args = process.argv.slice(2); -const foundryPath = args.find(arg => arg.startsWith('--foundry-path='))?.split('=')[1]; -const dataPath = args.find(arg => arg.startsWith('--data-path='))?.split('=')[1]; - -if (!foundryPath || !dataPath) { - console.log('Usage: npm run setup:dev -- --foundry-path="/path/to/foundry/main.js" --data-path="/path/to/data"'); - process.exit(1); -} - -const envContent = `FOUNDRY_MAIN_PATH=${foundryPath} -FOUNDRY_DATA_PATH=${dataPath} -`; - -fs.writeFileSync('.env', envContent); -console.log(`✅ Development environment configured: ${foundryPath}, ${dataPath}`); diff --git a/tools/run-start.mjs b/tools/run-start.mjs index 3f6b25cb..41002ffa 100644 --- a/tools/run-start.mjs +++ b/tools/run-start.mjs @@ -1,21 +1,10 @@ #!/usr/bin/env node import { spawn } from 'child_process'; +import { readEnvFile } from './util.mjs'; import fs from 'fs'; -// Load .env file if it exists -if (fs.existsSync('.env')) { - const envFile = fs.readFileSync('.env', 'utf8'); - envFile.split('\n').forEach(line => { - const [key, value] = line.split('='); - if (key && value) { - process.env[key] = value; - } - }); -} - -// Set defaults if not in environment -const foundryPath = process.env.FOUNDRY_MAIN_PATH || '../../../../FoundryDev/main.js'; -const dataPath = process.env.FOUNDRY_DATA_PATH || '../../../'; +// Load .env file params +const { foundryPath, dataPath } = readEnvFile(); // Run the original command with proper environment const args = ['rollup -c --watch', `node "\"${foundryPath}\"" --dataPath="${dataPath}" --noupnp`, 'gulp']; diff --git a/tools/util.mjs b/tools/util.mjs new file mode 100644 index 00000000..3af83540 --- /dev/null +++ b/tools/util.mjs @@ -0,0 +1,39 @@ +import fs from 'fs'; +import path from 'path'; + +export function readEnvFile() { + if (!fs.existsSync('.env')) { + console.error('No configured .env file. Copy .env.example to .env and configure it.'); + process.exit(); + } + + const envFile = fs.readFileSync('.env', 'utf8'); + envFile.split('\n').forEach(line => { + const [key, value] = line.split('='); + if (key && value) { + process.env[key] = value; + } + }); + + // Determine foundry path, handling if its an electron install (nested structure) + const foundryPath = path.normalize(process.env.FOUNDRY_MAIN_PATH); + const dataPath = path.normalize(process.env.FOUNDRY_DATA_PATH); + if (!foundryPath.endsWith(path.join('app', 'main.js'))) { + console.error('Configured FOUNDRY_MAIN_PATH is invalid, it must end with app/main.js'); + process.exit(); + } + if (/Data(\/|\\)?$/.test(dataPath) || !fs.existsSync(path.join(dataPath, 'Data'))) { + console.error('Configured FOUNDRY_DATA_PATH is incorrect. This must be a folder that contains "Data"'); + } + + return { + foundryPath, + foundryRoot: path.dirname(foundryPath), + dataPath + }; +} + +export function isContainedPath(parent, child) { + const relative = path.relative(parent, child); + return relative && !relative.startsWith('..') && !path.isAbsolute(relative); +} \ No newline at end of file