Compare commits

..

4 commits
2.3.2 ... main

Author SHA1 Message Date
Carlos Fernandez
8fec742da3
[Housekeeping] Fancier dev setup (#1980)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-06-06 21:52:45 -04:00
WBHarry
d030bfba34
Fixed so that only GMs can access the Countdown Edit menu (#1979) 2026-06-07 00:13:57 +02:00
Carlos Fernandez
a4428fd5be
Replace prettier with stylistic, improve types, and add no-undef rule (#1975)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-06-05 15:53:15 -04:00
WBHarry
6312a171e2
[Housekeeping] Styles Index:ification (#1977) 2026-06-05 15:36:07 -04:00
118 changed files with 1181 additions and 871 deletions

View file

@ -1,5 +1,6 @@
[*]
indent_size = 4
indent_style = spaces
end_of_line = lf
[*.yml]
indent_size = 2

View file

@ -1,2 +1,2 @@
FOUNDRY_MAIN_PATH=/path/to/foundry/resources/app/main.js
FOUNDRY_DATA_PATH=/path/to/foundry/data
FOUNDRY_DATA_PATH=/path/to/foundry/

View file

@ -1,13 +0,0 @@
{
"trailingComma": "none",
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 120,
"endOfLine": "lf",
"bracketSameLine": true
}

View file

@ -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="<path>" --data-path="<path>"` - Configure development environment
- `npm run createSymlink` - Setup types and output symlinks
### Notes

24
daggerheart.d.ts vendored
View file

@ -1,8 +1,11 @@
import '@client/global.mjs';
import '@common/global.mjs';
import '@common/primitives/global.mjs';
import Canvas from '@client/canvas/board.mjs';
// Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such
// This declare global hopefully fixes that
// Note: eslint is not aware of these, whatever is added here should go in the eslint's globals list
declare global {
/**
* A simple event framework used throughout Foundry Virtual Tabletop.
@ -12,9 +15,28 @@ declare global {
class Hooks extends foundry.helpers.Hooks {}
const fromUuid = foundry.utils.fromUuid;
const fromUuidSync = foundry.utils.fromUuidSync;
/**
* A representation of a color in hexadecimal format.
* This class provides methods for transformations and manipulations of colors.
*/
class Color extends foundry.utils.Color {}
/**
* The singleton game canvas
*/
const canvas: Canvas;
const ActiveEffect: foundry.documents.ActiveEffect;
const Actor: foundry.documents.Actor;
const BaseScene: foundry.documents.BaseScene;
const ChatMessage: foundry.documents.ChatMessage;
const Combat: foundry.documents.Combat;
const Combatant: foundry.documents.Combatant;
const Item: foundry.documents.Item;
const Macro: foundry.documents.Macro;
const Scene: foundry.documents.Scene;
const TokenDocument: foundry.documents.TokenDocument;
const Collection: foundry.utils.Collection;
const FormDataExtended: foundry.applications.ux.FormDataExtended;
const TextEditor: foundry.applications.ux.TextEditor;
}

View file

@ -1,14 +1,101 @@
import globals from 'globals';
import { defineConfig } from 'eslint/config';
import prettier from 'eslint-plugin-prettier';
import { defineConfig, globalIgnores } from 'eslint/config';
import tseslint from 'typescript-eslint';
import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
/** @type {Partial<RulesConfig>} */
export const stylisticRules = {
'@stylistic/indent': [
'error',
4,
{
SwitchCase: 1
}
],
'@stylistic/max-len': ['error', {
code: 120,
ignoreComments: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true
}],
'@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }],
'@stylistic/arrow-parens': ['error', 'as-needed'],
'@stylistic/quote-props': ['error', 'as-needed'],
'@stylistic/array-bracket-newline': ['error', 'consistent'],
'@stylistic/key-spacing': 'error',
'@stylistic/comma-dangle': ['error', 'never'],
'@stylistic/space-in-parens': ['error', 'never'],
'@stylistic/space-infix-ops': 2,
'@stylistic/keyword-spacing': 2,
'@stylistic/semi-spacing': 2,
'@stylistic/no-multi-spaces': 2,
'@stylistic/no-extra-semi': 2,
'@stylistic/no-whitespace-before-property': 2,
'@stylistic/space-unary-ops': 2
};
export default defineConfig([
{ files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: globals.browser } },
{ plugins: { prettier } },
globalIgnores(['foundry/**/*', 'build/**/*']),
{
files: ['gulpfile.js', 'postcss.config.js'],
languageOptions: { globals: globals.node }
},
{
files: ['**/*.{js,mjs,cjs}'],
rules: {
'prettier/prettier': 'error'
plugins: {
'@stylistic': stylistic
},
languageOptions: {
globals: {
...globals.browser,
CONFIG: 'readonly',
CONST: 'readonly',
// Global classes
Color: 'readonly',
Handlebars: 'readonly',
Hooks: 'readonly',
PIXI: 'readonly',
ProseMirror: 'readonly',
Roll: 'readonly',
// global namespaces
canvas: 'readonly',
foundry: 'readonly',
game: 'readonly',
ui: 'readonly',
// global functions
fromUuid: 'readonly',
fromUuidSync: 'readonly',
getDocumentClass: 'readonly',
_del: 'readonly',
_replace: 'readonly',
_loc: 'readonly',
// Documents
ActiveEffect: 'readonly',
Actor: 'readonly',
BaseScene: 'readonly',
ChatMessage: 'readonly',
Combat: 'readonly',
Combatant: 'readonly',
Item: 'readonly',
Macro: 'readonly',
Scene: 'readonly',
TokenDocument: 'readonly',
// Other
Collection: 'readonly',
FormDataExtended: 'readonly',
TextEditor: 'readonly'
}
},
rules: {
'no-undef': 'error',
// 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
...stylisticRules
}
},
{
files: ['**/*.ts'],
extends: [js.configs.recommended, tseslint.configs.recommended]
}
]);

View file

@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "ES6",
"target": "ES6",
"module": "es2022",
"target": "es2022",
"paths": {
"@client/*": ["./foundry/client/*"],
"@common/*": ["./foundry/common/*"]

View file

@ -450,7 +450,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
if (equipment.includes(type))
presets.filter = {
'system.tier': { key: 'system.tier', value: 1 },
'type': { key: 'type', value: type }
type: { key: 'type', value: type }
};
ui.compendiumBrowser.open(presets);

View file

@ -138,12 +138,12 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
const stressReductionStress = this.availableStressReductions
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
: 0;
const stress = this.actor.system.resources.stress;
context.stress =
selectedStressMarks.length > 0 || this.availableStressReductions
? {
value:
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
max: this.actor.system.resources.stress.max
value: stress.value + selectedStressMarks.length + stressReductionStress,
max: stress.max
}
: null;

View file

@ -135,192 +135,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
context.showTabs = this.tabGroups.primary !== 'summary';
break;
const actorArmor = this.actor.system.armor;
const levelKeys = Object.keys(this.levelup.levels);
let achivementProficiency = 0;
const achievementCards = [];
let achievementExperiences = [];
for (var levelKey of levelKeys) {
const level = this.levelup.levels[levelKey];
if (Number(levelKey) < this.levelup.startLevel) continue;
achivementProficiency += level.achievements.proficiency ?? 0;
const cards = level.achievements.domainCards ? Object.values(level.achievements.domainCards) : null;
if (cards) {
for (var card of cards) {
const itemCard = await foundry.utils.fromUuid(card.uuid);
achievementCards.push(itemCard);
}
}
achievementExperiences = level.achievements.experiences
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
if (experience.name) acc.push(experience);
return acc;
}, [])
: [];
}
context.achievements = {
proficiency: {
old: this.actor.system.proficiency,
new: this.actor.system.proficiency + achivementProficiency,
shown: achivementProficiency > 0
},
damageThresholds: {
major: {
old: this.actor.system.damageThresholds.major,
new: this.actor.system.damageThresholds.major + changedActorLevel - currentActorLevel
},
severe: {
old: this.actor.system.damageThresholds.severe,
new:
this.actor.system.damageThresholds.severe +
(actorArmor
? changedActorLevel - currentActorLevel
: (changedActorLevel - currentActorLevel) * 2)
},
unarmored: !actorArmor
},
domainCards: {
values: achievementCards,
shown: achievementCards.length > 0
},
experiences: {
values: achievementExperiences
}
};
const advancement = {};
for (var levelKey of levelKeys) {
const level = this.levelup.levels[levelKey];
if (Number(levelKey) < this.levelup.startLevel) continue;
for (var choiceKey of Object.keys(level.choices)) {
const choice = level.choices[choiceKey];
for (var checkbox of Object.values(choice)) {
switch (choiceKey) {
case 'proficiency':
case 'hitPoint':
case 'stress':
case 'evasion':
advancement[choiceKey] = advancement[choiceKey]
? advancement[choiceKey] + Number(checkbox.value)
: Number(checkbox.value);
break;
case 'trait':
if (!advancement[choiceKey]) advancement[choiceKey] = {};
for (var traitKey of checkbox.data) {
if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0;
advancement[choiceKey][traitKey] += 1;
}
break;
case 'domainCard':
if (!advancement[choiceKey]) advancement[choiceKey] = [];
if (checkbox.data.length === 1) {
const choiceItem = await foundry.utils.fromUuid(checkbox.data[0]);
advancement[choiceKey].push(choiceItem.toObject());
}
break;
case 'experience':
if (!advancement[choiceKey]) advancement[choiceKey] = [];
const data = checkbox.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(
x => x === data
);
return this.actor.system.experiences[experience]?.description ?? '';
});
advancement[choiceKey].push({ data: data, value: checkbox.value });
break;
case 'subclass':
if (checkbox.data[0]) {
const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
if (!advancement[choiceKey]) advancement[choiceKey] = [];
advancement[choiceKey].push({
...subclassItem.toObject(),
featureLabel: game.i18n.localize(
subclassFeatureLabels[Number(checkbox.secondaryData.featureState)]
)
});
}
break;
case 'multiclass':
const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
const subclass = multiclassItem
? await foundry.utils.fromUuid(checkbox.secondaryData.subclass)
: null;
advancement[choiceKey] = multiclassItem
? {
...multiclassItem.toObject(),
domain: checkbox.secondaryData.domain
? game.i18n.localize(
CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain]
.label
)
: null,
subclass: subclass ? subclass.name : null
}
: {};
break;
}
}
}
}
context.advancements = {
statistics: {
proficiency: {
old: context.achievements.proficiency.new,
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
},
hitPoints: {
old: this.actor.system.resources.hitPoints.max,
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
},
stress: {
old: this.actor.system.resources.stress.max,
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
},
evasion: {
old: this.actor.system.evasion,
new: this.actor.system.evasion + (advancement.evasion ?? 0)
}
},
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
if (advancement.trait?.[traitKey]) {
if (!acc) acc = {};
acc[traitKey] = {
label: game.i18n.localize(abilities[traitKey].label),
old: this.actor.system.traits[traitKey].value,
new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey]
};
}
return acc;
}, null),
domainCards: advancement.domainCard ?? [],
experiences:
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
[],
multiclass: advancement.multiclass,
subclass: advancement.subclass
};
context.advancements.statistics.proficiency.shown =
context.advancements.statistics.proficiency.new > context.advancements.statistics.proficiency.old;
context.advancements.statistics.hitPoints.shown =
context.advancements.statistics.hitPoints.new > context.advancements.statistics.hitPoints.old;
context.advancements.statistics.stress.shown =
context.advancements.statistics.stress.new > context.advancements.statistics.stress.old;
context.advancements.statistics.evasion.shown =
context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old;
context.advancements.statistics.shown =
context.advancements.statistics.proficiency.shown ||
context.advancements.statistics.hitPoints.shown ||
context.advancements.statistics.stress.shown ||
context.advancements.statistics.evasion.shown;
break;
}
return context;
@ -384,9 +198,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
this._dragDrop.forEach(d => d.bind(htmlElement));
}
tagifyUpdate =
type =>
async (_, { option, removed }) => {
tagifyUpdate = type => async (_, { option, removed }) => {
const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce(
(acc, choiceKey) => {
const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey];

View file

@ -306,7 +306,7 @@ export default class Party extends DHBaseActorSheet {
static async downtimeMoveQuery({ actorId, downtimeType }) {
const actor = await foundry.utils.fromUuid(actorId);
if (!actor || !actor?.isOwner) reject();
if (!actor || !actor?.isOwner) return;
new game.system.api.applications.dialogs.Downtime(actor, downtimeType === 'shortRest').render({
force: true
});

View file

@ -377,7 +377,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
action: 'update',
documentName: 'Item',
parent: targetActor,
updates: [{ '_id': existing.id, 'system.quantity': existing.system.quantity + quantity }]
updates: [{ _id: existing.id, 'system.quantity': existing.system.quantity + quantity }]
});
} else {
const itemsToCreate = [];
@ -410,7 +410,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
action: 'update',
documentName: 'Item',
parent: originActor,
updates: [{ '_id': item.id, 'system.quantity': item.system.quantity - quantity }]
updates: [{ _id: item.id, 'system.quantity': item.system.quantity - quantity }]
});
}

View file

@ -29,16 +29,6 @@ export default function ItemAttachmentSheet(Base) {
}
};
async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context);
if (partId === 'attachments') {
context.attachedItems = await prepareAttachmentContext(this.document);
}
return context;
}
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);

View file

@ -50,7 +50,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
const title =
(flavor ?? traitValue)
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
ability: game.i18n.localize(CONFIG.DH.ACTOR.abilities[traitValue].label)
})
: game.i18n.localize('DAGGERHEART.GENERAL.duality');

View file

@ -148,7 +148,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
}
async gmSetSetting(data) {
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data);
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }

View file

@ -212,7 +212,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
}
static async gmSetSetting(data) {
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data);
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }

View file

@ -223,7 +223,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @returns {object}
*/
async use(event, configOptions = {}) {
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
if (!this.actor) throw new Error('An Action can\'t be used outside of an Actor context.');
let config = this.prepareConfig(event, configOptions);
if (!config) return;

View file

@ -90,13 +90,13 @@ export default class BeastformEffect extends BaseEffect {
...baseUpdate,
x,
y,
'texture': {
texture: {
enabled: this.characterTokenData.usesDynamicToken,
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg,
scaleX: this.characterTokenData.tokenSize.scale,
scaleY: this.characterTokenData.tokenSize.scale
},
'ring': {
ring: {
subject: {
texture:
token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg

View file

@ -520,12 +520,12 @@ export default class DhCharacter extends DhCreature {
if (armorSource.type === 'armor') {
armorUpdates[armorSource.parent.id].updates.push({
'_id': armorSource.id,
_id: armorSource.id,
'system.armor.current': armorSource.system.armor.current + usedArmorChange
});
} else {
effectUpdates[armorSource.parent.id].updates.push({
'_id': armorSource.id,
_id: armorSource.id,
'system.changes': armorSource.system.changes.map(change => ({
...change,
value:

View file

@ -87,7 +87,7 @@ export default class CountdownField extends fields.ArrayField {
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
countdownSetting.toObject()
),
);
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }

View file

@ -324,7 +324,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
}
let x = 0.5 * bottom;
let y = 0.25;
for (let k = width - bottom; k--; ) {
for (let k = width - bottom; k--;) {
points.push(x, y);
x += 0.5;
y -= 0.25;
@ -333,7 +333,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
y += 0.25;
}
points.push(x, y);
for (let k = bottom; k--; ) {
for (let k = bottom; k--;) {
y += 0.5;
points.push(x, y);
x += 0.5;
@ -341,14 +341,14 @@ export default class DHToken extends CONFIG.Token.documentClass {
points.push(x, y);
}
y += 0.5;
for (let k = top; k--; ) {
for (let k = top; k--;) {
points.push(x, y);
x -= 0.5;
y += 0.25;
points.push(x, y);
y += 0.5;
}
for (let k = width - top; k--; ) {
for (let k = width - top; k--;) {
points.push(x, y);
x -= 0.5;
y += 0.25;
@ -357,7 +357,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
y -= 0.25;
}
points.push(x, y);
for (let k = top; k--; ) {
for (let k = top; k--;) {
y -= 0.5;
points.push(x, y);
x -= 0.5;
@ -365,7 +365,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
points.push(x, y);
}
y -= 0.5;
for (let k = bottom; k--; ) {
for (let k = bottom; k--;) {
points.push(x, y);
x += 0.5;
y -= 0.25;

551
package-lock.json generated
View file

@ -13,18 +13,20 @@
"rollup": "^4.40.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@foundryvtt/foundryvtt-cli": "^1.0.2",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@stylistic/eslint-plugin": "^5.10.0",
"concurrently": "^8.2.2",
"eslint": "^10.2.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.5.0",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"postcss": "^8.4.32",
"prettier": "^3.5.3",
"rollup-plugin-postcss": "^4.0.2"
"rollup-plugin-postcss": "^4.0.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.1"
}
},
"node_modules/@babel/runtime": {
@ -158,6 +160,27 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/js": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
"integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://eslint.org/donate"
},
"peerDependencies": {
"eslint": "^10.0.0"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
}
}
},
"node_modules/@eslint/object-schema": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
@ -420,19 +443,6 @@
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true
},
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@rollup/plugin-commonjs": {
"version": "25.0.8",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz",
@ -761,6 +771,58 @@
"util": "^0.12.4"
}
},
"node_modules/@stylistic/eslint-plugin": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz",
"integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/types": "^8.56.0",
"eslint-visitor-keys": "^4.2.1",
"espree": "^10.4.0",
"estraverse": "^5.3.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
"eslint": "^9.0.0 || ^10.0.0"
}
},
"node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@stylistic/eslint-plugin/node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.15.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@ -795,6 +857,288 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz",
"integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/type-utils": "8.60.1",
"@typescript-eslint/utils": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.60.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz",
"integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz",
"integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.60.1",
"@typescript-eslint/types": "^8.60.1",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz",
"integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz",
"integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz",
"integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1",
"@typescript-eslint/utils": "8.60.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz",
"integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz",
"integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.60.1",
"@typescript-eslint/tsconfig-utils": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
"integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz",
"integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz",
"integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@yaireo/tagify": {
"version": "4.35.1",
"resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.1.tgz",
@ -1853,10 +2197,11 @@
}
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
@ -2241,37 +2586,6 @@
}
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
"integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.1",
"synckit": "^0.11.12"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-scope": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
@ -2511,13 +2825,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@ -2554,6 +2861,24 @@
"reusify": "^1.0.4"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@ -5217,34 +5542,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
"integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -6053,22 +6350,6 @@
"node": ">= 10"
}
},
"node_modules/synckit": {
"version": "0.11.12",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
"integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/teex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
@ -6116,6 +6397,23 @@
"node": ">=18"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -6147,6 +6445,19 @@
"tree-kill": "cli.js"
}
},
"node_modules/ts-api-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.12"
},
"peerDependencies": {
"typescript": ">=4.8.4"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -6171,6 +6482,44 @@
"node": ">= 0.8.0"
}
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/typescript-eslint": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz",
"integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.60.1",
"@typescript-eslint/parser": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1",
"@typescript-eslint/utils": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",

View file

@ -18,24 +18,25 @@
"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"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@foundryvtt/foundryvtt-cli": "^1.0.2",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@stylistic/eslint-plugin": "^5.10.0",
"concurrently": "^8.2.2",
"eslint": "^10.2.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.5.0",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"postcss": "^8.4.32",
"prettier": "^3.5.3",
"rollup-plugin-postcss": "^4.0.2"
"rollup-plugin-postcss": "^4.0.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.1"
},
"lint-staged": {
"**/*": "eslint --fix"

View file

@ -1,19 +1,11 @@
@import './less/sheets/index.less';
@import './less/sheets-settings/index.less';
@import './less/dialog/index.less';
@import './less//hud/index.less';
@import './less/utils/colors.less';
@import './less/utils/fonts.less';
@import './less/hud/index.less';
@import './less/utils/index.less';
@import './less/global/index.less';
@import './less/ui/index.less';
@import './less/ux/index.less';
@import '../build/tagify.css';
@import './less/utils/mixin.less';

View file

@ -0,0 +1 @@
@import "./action-list.less";

View file

@ -0,0 +1 @@
@import "./sheet.less";

View file

@ -0,0 +1 @@
@import "./sheet.less";

View file

@ -0,0 +1,4 @@
@import "./sheet.less";
@import "./creation-action-footer.less";
@import "./selections-container.less";
@import "./tab-navigation.less";

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1,2 @@
@import './sheets.less';
@import './damage-reduction-container.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './death-move-container.less';

View file

@ -0,0 +1 @@
@import './roll-selection.less';

View file

@ -0,0 +1 @@
@import './downtime-container.less';

View file

@ -1,44 +0,0 @@
h1 {
color: @color-text-emphatic;
font: 700 var(--font-size-24) var(--dh-font-subtitle);
text-align: center;
}
header {
--bar-color: light-dark(@dark-blue, @golden);
color: light-dark(@dark, @beige);
display: flex;
justify-content: center;
align-items: center;
&:not(:first-child) {
margin-top: var(--spacer-8);
}
span {
padding: 0 10px;
}
&:before {
content: ' ';
flex: 1;
height: 1px;
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, var(--bar-color) 100%);
}
&:after {
content: ' ';
flex: 1;
height: 1px;
background: linear-gradient(90deg, var(--bar-color) 0%, rgba(0, 0, 0, 0) 100%);
}
}
img.portrait {
border-radius: 50%;
border: none;
object-fit: cover;
object-position: center top;
width: 2.5rem;
height: 2.5rem;
}

View file

@ -1,8 +1,3 @@
.daggerheart.dialog.dh-style.views.group-roll-dialog {
.window-content {
@import "./_common.less";
}
}
@import "./initialization.less";
@import "./main.less";
@import './sheet.less';
@import './initialization.less';
@import './main.less';

View file

@ -0,0 +1,48 @@
.daggerheart.dialog.dh-style.views.group-roll-dialog {
.window-content {
h1 {
color: @color-text-emphatic;
font: 700 var(--font-size-24) var(--dh-font-subtitle);
text-align: center;
}
header {
--bar-color: light-dark(@dark-blue, @golden);
color: light-dark(@dark, @beige);
display: flex;
justify-content: center;
align-items: center;
&:not(:first-child) {
margin-top: var(--spacer-8);
}
span {
padding: 0 10px;
}
&:before {
content: ' ';
flex: 1;
height: 1px;
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, var(--bar-color) 100%);
}
&:after {
content: ' ';
flex: 1;
height: 1px;
background: linear-gradient(90deg, var(--bar-color) 0%, rgba(0, 0, 0, 0) 100%);
}
}
img.portrait {
border-radius: 50%;
border: none;
object-fit: cover;
object-position: center top;
width: 2.5rem;
height: 2.5rem;
}
}
}

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -1,42 +1,20 @@
@import './attribution/sheet.less';
@import './level-up/index.less';
@import './resource-dice/sheet.less';
@import './actions/action-list.less';
@import './damage-selection/sheet.less';
@import './downtime/downtime-container.less';
@import './death-move/death-move-container.less';
@import './beastform/sheet.less';
@import './character-creation/creation-action-footer.less';
@import './character-creation/selections-container.less';
@import './character-creation/sheet.less';
@import './character-creation/tab-navigation.less';
@import './dice-roll/roll-selection.less';
@import './damage-reduction/damage-reduction-container.less';
@import './damage-reduction/sheets.less';
@import './multiclass-choice/sheet.less';
@import './tag-team-dialog/initialization.less';
@import './tag-team-dialog/sheet.less';
@import './actions/index.less';
@import './attribution/index.less';
@import './beastform/index.less';
@import './character-creation/index.less';
@import './character-reset/index.less';
@import './compendiumBrowserPackDialog/index.less';
@import './damage-reduction/index.less';
@import './damage-selection/index.less';
@import './death-move/index.less';
@import './dice-roll/index.less';
@import './downtime/index.less';
@import './group-roll-dialog/index.less';
@import './image-select/sheet.less';
@import './item-transfer/sheet.less';
@import './settings/change-currency-icon.less';
@import './risk-it-all/sheet.less';
@import './character-reset/sheet.less';
@import './compendiumBrowserPackDialog/sheet.less';
@import './level-up/index.less';
@import './resource-dice/index.less';
@import './multiclass-choice/index.less';
@import './tag-team-dialog/index.less';
@import './image-select/index.less';
@import './item-transfer/index.less';
@import './settings/index.less';
@import './risk-it-all/index.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './change-currency-icon.less';

View file

@ -0,0 +1,2 @@
@import './sheet.less';
@import './initialization.less';

View file

@ -1 +1 @@
@import './token-hud/token-hud.less';
@import './token-hud/index.less';

View file

@ -0,0 +1 @@
@import './token-hud.less';

View file

@ -0,0 +1,3 @@
@import './sheet.less';
@import './experiences.less';
@import './features.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1,2 @@
@import './adversaries.less';
@import './features.less';

View file

@ -1,8 +1,4 @@
@import './header.less';
@import './adversary-settings/sheet.less';
@import './adversary-settings/experiences.less';
@import './adversary-settings/features.less';
@import './character-settings/sheet.less';
@import './environment-settings/features.less';
@import './environment-settings/adversaries.less';
@import './adversary-settings/index.less';
@import './character-settings/index.less';
@import './environment-settings/index.less';

View file

@ -0,0 +1 @@
@import './actions.less';

View file

@ -0,0 +1 @@
@import './activeEffects.less';

View file

@ -0,0 +1,7 @@
@import './actor-sheet-shared.less';
@import './adversary/index.less';
@import './character/index.less';
@import './companion/index.less';
@import './environment/index.less';
@import './npc/index.less';
@import './party/index.less';

View file

@ -1,22 +1,5 @@
@import './actions/actions.less';
@import './actors/actor-sheet-shared.less';
@import './actors/adversary/index.less';
@import './actors/character/index.less';
@import './actors/companion/index.less';
@import './actors/environment/index.less';
@import './actors/npc/index.less';
@import './actors/party/index.less';
@import './items/beastform.less';
@import './items/class.less';
@import './items/domain-card.less';
@import './items/feature.less';
@import './items/heritage.less';
@import './items/item-sheet-shared.less';
@import './rollTables/sheet.less';
@import './actions/actions.less';
@import './activeEffects/activeEffects.less';
@import './activeEffects/index.less';
@import './actions/index.less';
@import './actors/index.less';
@import './items/index.less';
@import './rollTables/index.less';

View file

@ -0,0 +1,6 @@
@import './beastform.less';
@import './class.less';
@import './domain-card.less';
@import './feature.less';
@import './heritage.less';
@import './item-sheet-shared.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1,10 @@
@import './sheet.less';
@import './ability-use.less';
@import './action.less';
@import './chat.less';
@import './damage-summary.less';
@import './deathmoves.less';
@import './downtime.less';
@import './effect-summary.less';
@import './group-roll.less';
@import './refresh-message.less';

View file

@ -0,0 +1,5 @@
@import './combat-sidebar.less';
@import './combatant-controls.less';
@import './encounter-controls.less';
@import './spotlight-control.less';
@import './token-actions.less';

View file

@ -0,0 +1,3 @@
@import './sheet.less';
@import './countdown-edit.less';
@import './countdown.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -1,40 +1,11 @@
@import './chat/ability-use.less';
@import './chat/action.less';
@import './chat/chat.less';
@import './chat/damage-summary.less';
@import './chat/downtime.less';
@import './chat/effect-summary.less';
@import './chat/group-roll.less';
@import './chat/refresh-message.less';
@import './chat/deathmoves.less';
@import './chat/sheet.less';
@import './combat-sidebar/combat-sidebar.less';
@import './combat-sidebar/combatant-controls.less';
@import './combat-sidebar/encounter-controls.less';
@import './combat-sidebar/spotlight-control.less';
@import './combat-sidebar/token-actions.less';
@import './item-browser/item-browser.less';
@import './countdown/countdown.less';
@import './countdown/countdown-edit.less';
@import './countdown/sheet.less';
@import './ownership-selection/ownership-selection.less';
@import './resources/resources.less';
@import './settings/settings.less';
@import './settings/homebrew-settings/domains.less';
@import './settings/homebrew-settings/types.less';
@import './settings/homebrew-settings/resources.less';
@import './settings/appearance-settings/diceSoNice.less';
@import './sidebar/tabs.less';
@import './sidebar/daggerheartMenu.less';
@import './scene-config/scene-config.less';
@import './effects-display/sheet.less';
@import './scene-navigation/scene-navigation.less';
@import './chat/index.less';
@import './combat-sidebar/index.less';
@import './countdown/index.less';
@import './effects-display/index.less';
@import './item-browser/index.less';
@import './ownership-selection/index.less';
@import './resources/index.less';
@import './scene-config/index.less';
@import './scene-navigation/index.less';
@import './settings/index.less';
@import './sidebar/index.less';

View file

@ -0,0 +1 @@
@import './item-browser.less';

View file

@ -0,0 +1 @@
@import './ownership-selection.less';

View file

@ -0,0 +1 @@
@import './resources.less';

View file

@ -0,0 +1 @@
@import './scene-config.less';

View file

@ -0,0 +1 @@
@import './scene-navigation.less';

View file

@ -0,0 +1 @@
@import './diceSoNice.less';

View file

@ -0,0 +1,3 @@
@import './domains.less';
@import './resources.less';
@import './types.less';

View file

@ -0,0 +1,3 @@
@import './settings.less';
@import './appearance-settings/index.less';
@import './homebrew-settings/index.less';

View file

@ -0,0 +1,2 @@
@import './daggerheartMenu.less';
@import './tabs.less';

View file

@ -0,0 +1,4 @@
@import './colors.less';
@import './fonts.less';
@import './mixin.less';
@import './spacing.less';

View file

@ -0,0 +1 @@
@import './autocomplete.less';

View file

@ -1,10 +1,2 @@
@import './tooltip/sheet.less';
@import './tooltip/tooltip.less';
@import './tooltip/armorManagement.less';
@import './tooltip/battlepoints.less';
@import './tooltip/bordered-tooltip.less';
@import './tooltip/domain-cards.less';
@import './autocomplete/autocomplete.less';
@import './tooltip/resource-management.less';
@import './autocomplete/index.less';
@import './tooltip/index.less';

View file

@ -0,0 +1,7 @@
@import './sheet.less';
@import './armorManagement.less';
@import './battlepoints.less';
@import './bordered-tooltip.less';
@import './domain-cards.less';
@import './resource-management.less';
@import './tooltip.less';

View file

@ -2,9 +2,11 @@
<header class="countdowns-header">
<i class="fa-solid fa-clock-rotate-left" inert></i>
<span class="window-title">{{localize "DAGGERHEART.UI.Countdowns.title"}}</span>
{{#if isGM}}
<a class="header-control" data-tooltip aria-label="DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle" data-action="editCountdowns">
<i class="fa-solid fa-wrench" inert></i>
</a>
{{/if}}
<a class="header-control" data-tooltip aria-label="DAGGERHEART.UI.Countdowns.toggleIconMode" data-action="toggleViewMode">
<i class="fa-solid fa-down-left-and-up-right-to-center" inert></i>
</a>

View file

@ -82,7 +82,7 @@ function getMean(numbers) {
}
function getMedianAverageDeviation(numbers, { median }) {
const residuals = allDamage.map(d => Math.abs(d - median));
const residuals = numbers.map(d => Math.abs(d - median));
return getMedian(residuals);
}

View file

@ -1,45 +1,70 @@
import fs from 'fs';
import path from 'path';
import readline from 'readline';
import { isContainedPath, readEnvFile } from './util.mjs';
const askQuestion = question => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const projectRoot = path.resolve(import.meta.dirname, '../')
const { foundryRoot, dataPath } = readEnvFile();
return new Promise(resolve =>
rl.question(question, answer => {
rl.close();
resolve(answer);
})
);
};
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;
}
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) {
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(); // Add empty newline
await createDaggerheartSymlink();

View file

@ -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}`);

20
tools/eslint.config.mjs Normal file
View file

@ -0,0 +1,20 @@
import globals from 'globals';
import { defineConfig, globalIgnores } from 'eslint/config';
import { stylisticRules } from '../eslint.config.mjs';
import stylistic from '@stylistic/eslint-plugin';
export default defineConfig([
globalIgnores(['foundry/**/*']),
{
files: ['**/*.{js,mjs,cjs}'],
plugins: {
'@stylistic': stylistic
},
languageOptions: { globals: globals.node },
rules: {
'no-undef': 'error',
'no-unused-vars': 0,
...stylisticRules
}
}
]);

View file

@ -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'];

39
tools/util.mjs Normal file
View file

@ -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);
}